From f0065a0906027777ff146c1411f5551380a4a240 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Mon, 23 Mar 2026 20:38:09 +0200 Subject: [PATCH 001/106] (---section submitted PRs START) From c84e5e6a0650551aaa321bd8a3d5072eefe4e4a0 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Mon, 23 Mar 2026 20:38:09 +0200 Subject: [PATCH 002/106] (---section submitted PRs STOP) From 9011bd3193342384c989b795aef609877a141206 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Fri, 27 Mar 2026 13:05:36 +0200 Subject: [PATCH 003/106] (---section: user LL infra START) From 137577434d96cb322ebe662662f6fa98999f3564 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Tue, 10 Feb 2026 15:17:16 +0200 Subject: [PATCH 004/106] ipc: move standalone-test check later in ipc_init() If CONFIG_SOF_BOOT_TEST_STANDALONE is set, ipc_init() is terminated early. This ensures SOF will not start to generate or respond to IPC messages that could potentially interfere with standalone test cases (some of which send and receive IPCs). The current implementation leaves the component list uninitialized and this can cause trouble to standalone tests that want to utilzie common IPC code to build messages. Fix this problem by executing more of ipc_init() also in the standalone mode. Signed-off-by: Kai Vehmanen --- src/ipc/ipc-common.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ipc/ipc-common.c b/src/ipc/ipc-common.c index 10f241784625..a62223dc97a2 100644 --- a/src/ipc/ipc-common.c +++ b/src/ipc/ipc-common.c @@ -294,11 +294,6 @@ __cold int ipc_init(struct sof *sof) tr_dbg(&ipc_tr, "entry"); -#if CONFIG_SOF_BOOT_TEST_STANDALONE - LOG_INF("SOF_BOOT_TEST_STANDALONE, disabling IPC."); - return 0; -#endif - /* init ipc data */ sof->ipc = rzalloc(SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, sizeof(*sof->ipc)); if (!sof->ipc) { @@ -329,6 +324,11 @@ __cold int ipc_init(struct sof *sof) io_perf_monitor_init_data(&sof->ipc->io_perf_out_msg_count, &init_data); #endif +#if CONFIG_SOF_BOOT_TEST_STANDALONE + LOG_INF("SOF_BOOT_TEST_STANDALONE, disabling IPC."); + return 0; +#endif + #ifdef __ZEPHYR__ struct k_thread *thread = &sof->ipc->ipc_send_wq.thread; From 4992c5c6a373446d371d5412ca620c4f5260dd8e Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Fri, 13 Feb 2026 14:54:58 +0200 Subject: [PATCH 005/106] audio: component: add comp_grant_access_to_thread() Add a method to add access to a component object to a particular thread. This is required as component object state includes kernel objects and to use these from user-space, access needs to be granted. Signed-off-by: Kai Vehmanen --- src/audio/component.c | 8 ++++++++ src/include/sof/audio/component.h | 10 ++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/audio/component.c b/src/audio/component.c index 941be20f8534..83d35bc18a1c 100644 --- a/src/audio/component.c +++ b/src/audio/component.c @@ -699,3 +699,11 @@ void comp_update_ibs_obs_cpc(struct comp_dev *dev) #endif } +#ifdef CONFIG_SOF_USERSPACE_LL +void comp_grant_access_to_thread(const struct comp_dev *dev, struct k_thread *th) +{ + assert(dev->list_mutex); + tr_dbg(&ipc_tr, "grant access to mutex %p for thread %p", dev->list_mutex, th); + k_thread_access_grant(th, dev->list_mutex); +} +#endif diff --git a/src/include/sof/audio/component.h b/src/include/sof/audio/component.h index b6695b9cd312..4bc65046fe2e 100644 --- a/src/include/sof/audio/component.h +++ b/src/include/sof/audio/component.h @@ -1228,6 +1228,16 @@ void comp_init_performance_data(struct comp_dev *dev); */ bool comp_update_performance_data(struct comp_dev *dev, uint32_t cycles_used); +/** + * Grant access to component to a thread. + * + * Must be called from kernel context. + * + * @param dev Component to update. + * @param th thread to give access to + */ +void comp_grant_access_to_thread(const struct comp_dev *dev, struct k_thread *th); + static inline int user_get_buffer_memory_region(const struct comp_driver *drv) { #if CONFIG_SOF_USERSPACE_USE_DRIVER_HEAP From 4b8829502f07f6e7001f667049ccd71709b62c2d Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 12 Feb 2026 21:16:34 +0200 Subject: [PATCH 006/106] pipeline: protect component connections with a mutex In userspace LL builds, use a mutex to protect component connections. This code shoudl work for kernel builds, but at least now add new code under a ifdef to avoid the cost of additional mutex object. Signed-off-by: Kai Vehmanen --- src/audio/pipeline/pipeline-graph.c | 22 ++++++++++++++++------ src/include/sof/audio/component.h | 9 +++++++++ src/include/sof/audio/component_ext.h | 4 ++++ 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/audio/pipeline/pipeline-graph.c b/src/audio/pipeline/pipeline-graph.c index 89bb3574289b..39125b6346e3 100644 --- a/src/audio/pipeline/pipeline-graph.c +++ b/src/audio/pipeline/pipeline-graph.c @@ -178,24 +178,34 @@ static void buffer_set_comp(struct comp_buffer *buffer, struct comp_dev *comp, comp_buffer_set_sink_component(buffer, comp); } +#ifdef CONFIG_SOF_USERSPACE_LL +#define PPL_LOCK_DECLARE +#define PPL_LOCK(x) k_mutex_lock(comp->list_mutex, K_FOREVER) +#define PPL_UNLOCK(x) k_mutex_unlock(comp->list_mutex) +#else +#define PPL_LOCK_DECLARE uint32_t flags +#define PPL_LOCK(x) irq_local_disable(flags) +#define PPL_UNLOCK(x) irq_local_enable(flags) +#endif + int pipeline_connect(struct comp_dev *comp, struct comp_buffer *buffer, int dir) { struct list_item *comp_list; - uint32_t flags; + PPL_LOCK_DECLARE; if (dir == PPL_CONN_DIR_COMP_TO_BUFFER) comp_info(comp, "connect buffer %d as sink", buf_get_id(buffer)); else comp_info(comp, "connect buffer %d as source", buf_get_id(buffer)); - irq_local_disable(flags); + PPL_LOCK(); comp_list = comp_buffer_list(comp, dir); buffer_attach(buffer, comp_list, dir); buffer_set_comp(buffer, comp, dir); - irq_local_enable(flags); + PPL_UNLOCK(); return 0; } @@ -203,20 +213,20 @@ int pipeline_connect(struct comp_dev *comp, struct comp_buffer *buffer, void pipeline_disconnect(struct comp_dev *comp, struct comp_buffer *buffer, int dir) { struct list_item *comp_list; - uint32_t flags; + PPL_LOCK_DECLARE; if (dir == PPL_CONN_DIR_COMP_TO_BUFFER) comp_dbg(comp, "disconnect buffer %d as sink", buf_get_id(buffer)); else comp_dbg(comp, "disconnect buffer %d as source", buf_get_id(buffer)); - irq_local_disable(flags); + PPL_LOCK(); comp_list = comp_buffer_list(comp, dir); buffer_detach(buffer, comp_list, dir); buffer_set_comp(buffer, NULL, dir); - irq_local_enable(flags); + PPL_UNLOCK(); } /* pipelines must be inactive */ diff --git a/src/include/sof/audio/component.h b/src/include/sof/audio/component.h index 4bc65046fe2e..a69be34b15a1 100644 --- a/src/include/sof/audio/component.h +++ b/src/include/sof/audio/component.h @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -679,6 +680,10 @@ struct comp_dev { struct list_item bsource_list; /**< list of source buffers */ struct list_item bsink_list; /**< list of sink buffers */ +#ifdef CONFIG_SOF_USERSPACE_LL + struct k_mutex *list_mutex; /**< protect lists of source/sinks */ +#endif + /* performance data*/ struct comp_perf_data perf_data; /* Input Buffer Size for pin 0, add array for other pins if needed */ @@ -863,6 +868,10 @@ static inline void comp_init(const struct comp_driver *drv, dev->state = COMP_STATE_INIT; list_init(&dev->bsink_list); list_init(&dev->bsource_list); +#ifdef CONFIG_SOF_USERSPACE_LL + dev->list_mutex = k_object_alloc(K_OBJ_MUTEX); + k_mutex_init(dev->list_mutex); +#endif memcpy_s(&dev->tctx, sizeof(dev->tctx), trace_comp_drv_get_tr_ctx(dev->drv), sizeof(struct tr_ctx)); } diff --git a/src/include/sof/audio/component_ext.h b/src/include/sof/audio/component_ext.h index d2bbf87a7764..d0afe4be3ad8 100644 --- a/src/include/sof/audio/component_ext.h +++ b/src/include/sof/audio/component_ext.h @@ -53,6 +53,10 @@ static inline void comp_free(struct comp_dev *dev) * be freed after this. */ drv->ops.free(dev); + +#ifdef CONFIG_SOF_USERSPACE_LL + k_object_free(dev->list_mutex); +#endif } /** From 7aa0732d2ca85fef9c69d2dacdc240bee44c1228 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Wed, 22 Apr 2026 15:32:19 +0300 Subject: [PATCH 007/106] audio: component: replace k_mutex with sys_mutex for list_mutex - replace dynamically-allocated k_mutex *list_mutex with embedded sys_mutex - remove k_object_alloc in comp_init, k_object_release in comp_free - remove comp_grant_access_to_thread() function and caller - update PPL_LOCK/PPL_UNLOCK and ipc-helper lock sites Signed-off-by: Kai Vehmanen --- src/audio/component.c | 9 +-------- src/audio/pipeline/pipeline-graph.c | 4 ++-- src/include/sof/audio/component.h | 15 ++------------- src/include/sof/audio/component_ext.h | 4 ---- src/ipc/ipc-helper.c | 13 +++++++++++++ 5 files changed, 18 insertions(+), 27 deletions(-) diff --git a/src/audio/component.c b/src/audio/component.c index 83d35bc18a1c..058bf733ae5c 100644 --- a/src/audio/component.c +++ b/src/audio/component.c @@ -699,11 +699,4 @@ void comp_update_ibs_obs_cpc(struct comp_dev *dev) #endif } -#ifdef CONFIG_SOF_USERSPACE_LL -void comp_grant_access_to_thread(const struct comp_dev *dev, struct k_thread *th) -{ - assert(dev->list_mutex); - tr_dbg(&ipc_tr, "grant access to mutex %p for thread %p", dev->list_mutex, th); - k_thread_access_grant(th, dev->list_mutex); -} -#endif + diff --git a/src/audio/pipeline/pipeline-graph.c b/src/audio/pipeline/pipeline-graph.c index 39125b6346e3..cf493bfb6ea4 100644 --- a/src/audio/pipeline/pipeline-graph.c +++ b/src/audio/pipeline/pipeline-graph.c @@ -180,8 +180,8 @@ static void buffer_set_comp(struct comp_buffer *buffer, struct comp_dev *comp, #ifdef CONFIG_SOF_USERSPACE_LL #define PPL_LOCK_DECLARE -#define PPL_LOCK(x) k_mutex_lock(comp->list_mutex, K_FOREVER) -#define PPL_UNLOCK(x) k_mutex_unlock(comp->list_mutex) +#define PPL_LOCK(x) sys_mutex_lock(&comp->list_mutex, K_FOREVER) +#define PPL_UNLOCK(x) sys_mutex_unlock(&comp->list_mutex) #else #define PPL_LOCK_DECLARE uint32_t flags #define PPL_LOCK(x) irq_local_disable(flags) diff --git a/src/include/sof/audio/component.h b/src/include/sof/audio/component.h index a69be34b15a1..82b9599df8c8 100644 --- a/src/include/sof/audio/component.h +++ b/src/include/sof/audio/component.h @@ -681,7 +681,7 @@ struct comp_dev { struct list_item bsink_list; /**< list of sink buffers */ #ifdef CONFIG_SOF_USERSPACE_LL - struct k_mutex *list_mutex; /**< protect lists of source/sinks */ + struct sys_mutex list_mutex; /**< protect lists of source/sinks */ #endif /* performance data*/ @@ -869,8 +869,7 @@ static inline void comp_init(const struct comp_driver *drv, list_init(&dev->bsink_list); list_init(&dev->bsource_list); #ifdef CONFIG_SOF_USERSPACE_LL - dev->list_mutex = k_object_alloc(K_OBJ_MUTEX); - k_mutex_init(dev->list_mutex); + sys_mutex_init(&dev->list_mutex); #endif memcpy_s(&dev->tctx, sizeof(dev->tctx), trace_comp_drv_get_tr_ctx(dev->drv), sizeof(struct tr_ctx)); @@ -1237,16 +1236,6 @@ void comp_init_performance_data(struct comp_dev *dev); */ bool comp_update_performance_data(struct comp_dev *dev, uint32_t cycles_used); -/** - * Grant access to component to a thread. - * - * Must be called from kernel context. - * - * @param dev Component to update. - * @param th thread to give access to - */ -void comp_grant_access_to_thread(const struct comp_dev *dev, struct k_thread *th); - static inline int user_get_buffer_memory_region(const struct comp_driver *drv) { #if CONFIG_SOF_USERSPACE_USE_DRIVER_HEAP diff --git a/src/include/sof/audio/component_ext.h b/src/include/sof/audio/component_ext.h index d0afe4be3ad8..d2bbf87a7764 100644 --- a/src/include/sof/audio/component_ext.h +++ b/src/include/sof/audio/component_ext.h @@ -53,10 +53,6 @@ static inline void comp_free(struct comp_dev *dev) * be freed after this. */ drv->ops.free(dev); - -#ifdef CONFIG_SOF_USERSPACE_LL - k_object_free(dev->list_mutex); -#endif } /** diff --git a/src/ipc/ipc-helper.c b/src/ipc/ipc-helper.c index 2f685b551747..cc0f7f929bb9 100644 --- a/src/ipc/ipc-helper.c +++ b/src/ipc/ipc-helper.c @@ -333,7 +333,16 @@ __cold int ipc_comp_free(struct ipc *ipc, uint32_t comp_id) return -EINVAL; } + /* Lock buffer lists to prevent racing with the LL scheduler. + * In user-space builds, irq_local_disable() is a privileged + * operation, so use the per-component list_mutex instead + * (same pattern as PPL_LOCK in pipeline_disconnect()). + */ +#ifdef CONFIG_SOF_USERSPACE_LL + sys_mutex_lock(&icd->cd->list_mutex, K_FOREVER); +#else irq_local_disable(flags); +#endif comp_dev_for_each_producer_safe(icd->cd, buffer, safe) { comp_buffer_set_sink_component(buffer, NULL); /* This breaks the list, but we anyway delete all buffers */ @@ -346,7 +355,11 @@ __cold int ipc_comp_free(struct ipc *ipc, uint32_t comp_id) comp_buffer_reset_source_list(buffer); } +#ifdef CONFIG_SOF_USERSPACE_LL + sys_mutex_unlock(&icd->cd->list_mutex); +#else irq_local_enable(flags); +#endif /* free component and remove from list */ comp_free(icd->cd); From ceb9685c4705322c03ff0bbe37b1faca18026356 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Wed, 18 Mar 2026 18:35:47 +0200 Subject: [PATCH 008/106] rtos: alloc.h: add sof_sys_user_heap_get() Add sof_sys_user_heap_get() to get heap object to use for SOF audio application heap allocations. When SOF is built with CONFIG_SOF_USERSPACE_LL, this will return a heap that can be used for user-space code. Signed-off-by: Kai Vehmanen --- posix/include/rtos/alloc.h | 1 + src/platform/library/lib/alloc.c | 5 +++++ zephyr/include/rtos/alloc.h | 11 +++++++++++ zephyr/lib/alloc.c | 11 +++++++++++ 4 files changed, 28 insertions(+) diff --git a/posix/include/rtos/alloc.h b/posix/include/rtos/alloc.h index b8efac0d3112..60dc951876e0 100644 --- a/posix/include/rtos/alloc.h +++ b/posix/include/rtos/alloc.h @@ -141,6 +141,7 @@ 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); struct k_heap *sof_sys_heap_get(void); +struct k_heap *sof_sys_user_heap_get(void); /** * Calculates length of the null-terminated string. diff --git a/src/platform/library/lib/alloc.c b/src/platform/library/lib/alloc.c index 74cb926e4aff..804c7a2f4f6e 100644 --- a/src/platform/library/lib/alloc.c +++ b/src/platform/library/lib/alloc.c @@ -75,3 +75,8 @@ struct k_heap *sof_sys_heap_get(void) { return NULL; } + +struct k_heap *sof_sys_user_heap_get(void) +{ + return NULL; +} diff --git a/zephyr/include/rtos/alloc.h b/zephyr/include/rtos/alloc.h index 116789ea6ead..f42e3ede02bb 100644 --- a/zephyr/include/rtos/alloc.h +++ b/zephyr/include/rtos/alloc.h @@ -127,6 +127,17 @@ void *sof_heap_alloc(struct k_heap *heap, uint32_t flags, size_t bytes, void sof_heap_free(struct k_heap *heap, void *addr); struct k_heap *sof_sys_heap_get(void); +/** + * Returns heap object to use for SOF heap allocations + * for audio application code. + * + * This should not be used for heap allocations for objects that + * are only used in SOF kernel space. + * + * Note: audio modules should use mod_alloc() instead! + */ +struct k_heap *sof_sys_user_heap_get(void); + /* TODO: remove - debug only - only needed for linking */ static inline void heap_trace_all(int force) {} diff --git a/zephyr/lib/alloc.c b/zephyr/lib/alloc.c index ebf29ffbf42a..fc6002b9dc9f 100644 --- a/zephyr/lib/alloc.c +++ b/zephyr/lib/alloc.c @@ -13,6 +13,7 @@ #include #include #include +#include /* for zephyr_ll_user_heap() */ #include #include #include @@ -378,6 +379,16 @@ struct k_heap *sof_sys_heap_get(void) return &sof_heap; } +struct k_heap *sof_sys_user_heap_get(void) +{ +#ifdef CONFIG_SOF_USERSPACE_LL + return zephyr_ll_user_heap(); +#else + /* let sof_heap_alloc() pick */ + return NULL; +#endif +} + static void *heap_alloc_aligned(struct k_heap *h, size_t min_align, size_t bytes) { k_spinlock_key_t key; From 225c89c4197847e1afb17ee9b33da2582cdebbfb Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 26 Mar 2026 15:20:06 +0200 Subject: [PATCH 009/106] zephyr: rtos: userspace_helper.h: add sysuser memory partition Add APP_SYSUSER_BSS() and APP_SYSUSER_DATA() macros to put global objects to a memory partition that is available to the user context used to run main SOF application. If SOF is run in kernel space, these are no-ops, but if CONFIG_SOF_USERSPACE_LL is set, a dedicated memory partition is used. Signed-off-by: Kai Vehmanen --- zephyr/include/rtos/userspace_helper.h | 25 +++++++++++++++++++++++++ zephyr/lib/userspace_helper.c | 8 ++++++++ 2 files changed, 33 insertions(+) diff --git a/zephyr/include/rtos/userspace_helper.h b/zephyr/include/rtos/userspace_helper.h index 29635fb942ad..6951d6dcdd43 100644 --- a/zephyr/include/rtos/userspace_helper.h +++ b/zephyr/include/rtos/userspace_helper.h @@ -26,6 +26,9 @@ #define APP_TASK_BSS K_APP_BMEM(common_partition) #define APP_TASK_DATA K_APP_DMEM(common_partition) +#define APP_SYSUSER_BSS K_APP_BMEM(sysuser_partition) +#define APP_SYSUSER_DATA K_APP_DMEM(sysuser_partition) + struct processing_module; struct userspace_context; @@ -137,4 +140,26 @@ static inline int user_access_to_mailbox(struct k_mem_domain *domain, k_tid_t th #endif /* CONFIG_USERSPACE */ +#ifdef CONFIG_SOF_USERSPACE_LL + +int user_memory_attach_system_user_partition(struct k_mem_domain *dom); + +#else + +/** + * Attach SOF system user memory partition to a memory domain. + * @param dom - memory domain to attach the common partition to. + * + * @return 0 for success, error otherwise. + * + * @note + * Function used only when CONFIG_USERSPACE is set. + * The common partition contains shared objects required by user-space modules. + */ +static int user_memory_attach_system_user_partition(struct k_mem_domain *dom) +{ +} + +#endif /* CONFIG_SOF_USERSPACE_LL */ + #endif /* __ZEPHYR_LIB_USERSPACE_HELPER_H__ */ diff --git a/zephyr/lib/userspace_helper.c b/zephyr/lib/userspace_helper.c index 8c4aef423e15..c7c361295269 100644 --- a/zephyr/lib/userspace_helper.c +++ b/zephyr/lib/userspace_helper.c @@ -36,6 +36,10 @@ LOG_MODULE_REGISTER(userspace_helper, CONFIG_SOF_LOG_LEVEL); K_APPMEM_PARTITION_DEFINE(common_partition); +#ifdef CONFIG_SOF_USERSPACE_LL +K_APPMEM_PARTITION_DEFINE(sysuser_partition); +#endif + struct k_heap *module_driver_heap_init(void) { struct k_heap *mod_drv_heap = rballoc(SOF_MEM_FLAG_USER, sizeof(*mod_drv_heap)); @@ -83,6 +87,10 @@ int user_memory_attach_common_partition(struct k_mem_domain *dom) } #ifdef CONFIG_SOF_USERSPACE_LL +int user_memory_attach_system_user_partition(struct k_mem_domain *dom) +{ + return k_mem_domain_add_partition(dom, &sysuser_partition); +} int user_access_to_mailbox(struct k_mem_domain *domain, k_tid_t thread_id) { From 4c57ab1ab035307af40493068ed2239230aacbd7 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Fri, 13 Feb 2026 16:28:55 +0200 Subject: [PATCH 010/106] zephyr: lib: make sof_heap_alloc/free system calls Add a built option to make sof_heap_allo/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 | 15 ++++-- zephyr/lib/alloc.c | 6 +-- zephyr/syscall/alloc.c | 20 +++++++ zephyr/test/CMakeLists.txt | 3 ++ zephyr/test/userspace/test_heap_alloc.c | 72 +++++++++++++++++++++++++ 7 files changed, 119 insertions(+), 6 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 9a4737858910..f26192bd09a0 100644 --- a/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -549,6 +549,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(syscall/alloc.c) zephyr_library_link_libraries(SOF) target_link_libraries(SOF INTERFACE zephyr_interface) diff --git a/zephyr/Kconfig b/zephyr/Kconfig index e231885788fe..d494da791590 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 f42e3ede02bb..d261165e786b 100644 --- a/zephyr/include/rtos/alloc.h +++ b/zephyr/include/rtos/alloc.h @@ -122,9 +122,16 @@ void rfree(void *ptr); */ void l3_heap_save(void); -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); +__syscall 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); + +__syscall void sof_heap_free(struct k_heap *heap, void *addr); + +void z_impl_sof_heap_free(struct k_heap *heap, void *addr); + struct k_heap *sof_sys_heap_get(void); /** @@ -160,4 +167,6 @@ size_t get_shared_buffer_heap_size(void); #endif +#include + #endif /* __ZEPHYR_RTOS_ALLOC_H__ */ diff --git a/zephyr/lib/alloc.c b/zephyr/lib/alloc.c index fc6002b9dc9f..30d7ed38025c 100644 --- a/zephyr/lib/alloc.c +++ b/zephyr/lib/alloc.c @@ -626,8 +626,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); @@ -641,7 +641,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); diff --git a/zephyr/syscall/alloc.c b/zephyr/syscall/alloc.c new file mode 100644 index 000000000000..fad39865b9d2 --- /dev/null +++ b/zephyr/syscall/alloc.c @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 Intel Corporation. + +#include +#include +#include + +static inline void *z_vrfy_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); +} +#include + +static inline void z_vrfy_sof_heap_free(struct k_heap *heap, void *addr) +{ + 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 e51fc1efcfb54d2b059190e6d0eedf6084f31491 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Fri, 13 Feb 2026 17:44:14 +0200 Subject: [PATCH 011/106] schedule: zephyr_ll_user: make the heap accessible from user-space Separate the state for LL scheduler memory into kernel and user accessible resources. The pointer to the LL heap must be accessible from user-space, so that user space can allocate memory and pass the heap pointer as argument. Signed-off-by: Kai Vehmanen --- src/schedule/zephyr_ll_user.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/schedule/zephyr_ll_user.c b/src/schedule/zephyr_ll_user.c index aa33807b4aa3..f915fd506a4d 100644 --- a/src/schedule/zephyr_ll_user.c +++ b/src/schedule/zephyr_ll_user.c @@ -17,15 +17,18 @@ 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 */ }; static struct zephyr_ll_mem_resources ll_mem_resources; +K_APPMEM_PARTITION_DEFINE(ll_common); +/* Heap allocator for LL scheduler memory (user accessible pointer) */ +K_APP_BMEM(ll_common) static struct k_heap *zephyr_ll_heap; + static struct k_heap *zephyr_ll_heap_init(void) { struct k_heap *heap = module_driver_heap_init(); @@ -61,6 +64,11 @@ static struct k_heap *zephyr_ll_heap_init(void) if (ret) k_panic(); + ret = k_mem_domain_add_partition(&ll_mem_resources.mem_domain, &ll_common); + tr_dbg(&ll_tr, "init ll common %p, ret %d", (void *)&ll_common, ret); + if (ret) + k_panic(); + return heap; } @@ -68,15 +76,16 @@ void zephyr_ll_user_resources_init(void) { k_mem_domain_init(&ll_mem_resources.mem_domain, 0, NULL); - ll_mem_resources.heap = zephyr_ll_heap_init(); + zephyr_ll_heap = zephyr_ll_heap_init(); /* attach common partition to LL domain */ user_memory_attach_common_partition(zephyr_ll_mem_domain()); + user_memory_attach_system_user_partition(zephyr_ll_mem_domain()); } struct k_heap *zephyr_ll_user_heap(void) { - return ll_mem_resources.heap; + return zephyr_ll_heap; } struct k_mem_domain *zephyr_ll_mem_domain(void) From 4592862c9048814171e43afe1375e3a56a0194e7 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Fri, 13 Feb 2026 18:48:43 +0200 Subject: [PATCH 012/106] schedule: zephyr_ll: convert pdata->sem into a dynamic object Turn the pdata->sem into a dynamic object in userspace LL builds. Keep statically allocated semaphore for kernel LL builds. Signed-off-by: Kai Vehmanen --- src/schedule/zephyr_ll.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/schedule/zephyr_ll.c b/src/schedule/zephyr_ll.c index 575a82d91dda..e2d02f12926b 100644 --- a/src/schedule/zephyr_ll.c +++ b/src/schedule/zephyr_ll.c @@ -40,7 +40,10 @@ struct zephyr_ll { struct zephyr_ll_pdata { bool run; bool freeing; - struct k_sem sem; +#ifndef CONFIG_SOF_USERSPACE_LL + struct k_sem sem_obj; +#endif + struct k_sem *sem; }; static void zephyr_ll_lock(struct zephyr_ll *sch, uint32_t *flags) @@ -87,7 +90,7 @@ static void zephyr_ll_task_done(struct zephyr_ll *sch, * zephyr_ll_task_free() is trying to free this task. Complete * it and signal the semaphore to let the function proceed */ - k_sem_give(&pdata->sem); + k_sem_give(pdata->sem); tr_info(&ll_tr, "task complete %p %pU", task, task->uid); tr_info(&ll_tr, "num_tasks %d total_num_tasks %ld", @@ -454,7 +457,11 @@ static int zephyr_ll_task_free(void *data, struct task *task) if (must_wait) /* Wait for up to 100 periods */ - k_sem_take(&pdata->sem, K_USEC(LL_TIMER_PERIOD_US * 100)); + k_sem_take(pdata->sem, K_USEC(LL_TIMER_PERIOD_US * 100)); + +#ifdef CONFIG_SOF_USERSPACE_LL + k_object_free(pdata->sem); +#endif /* Protect against racing with schedule_task() */ zephyr_ll_lock(sch, &flags); @@ -560,7 +567,12 @@ int zephyr_ll_task_init(struct task *task, memset(pdata, 0, sizeof(*pdata)); - k_sem_init(&pdata->sem, 0, 1); +#ifdef CONFIG_SOF_USERSPACE_LL + pdata->sem = k_object_alloc(K_OBJ_SEM); +#else + pdata->sem = &pdata->sem_obj; +#endif + k_sem_init(pdata->sem, 0, 1); task->priv_data = pdata; From dd8ab4c8af8bdd8b6f0db11978c5f2382a5230b8 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Wed, 22 Apr 2026 15:47:30 +0300 Subject: [PATCH 013/106] schedule: zephyr_ll: replace dynamic k_sem with embedded sys_sem Replace the dynamically-allocated k_sem (via k_object_alloc) in zephyr_ll_pdata with an embedded sys_sem. The dynamic kernel semaphore could not be freed from user-space threads, causing kernel object leaks that exhausted the kernel object pool during long test runs. sys_sem uses k_futex in user mode and lives in user memory, so no kernel object allocation or cleanup is needed. Add POSIX no-op stubs for sys_sem to maintain testbench build compatibility. Signed-off-by: Kai Vehmanen --- posix/include/rtos/mutex.h | 21 +++++++++++++++++++++ src/schedule/zephyr_ll.c | 21 +++++---------------- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/posix/include/rtos/mutex.h b/posix/include/rtos/mutex.h index 19b360bdaea5..3bd01342ced5 100644 --- a/posix/include/rtos/mutex.h +++ b/posix/include/rtos/mutex.h @@ -62,4 +62,25 @@ static inline int sys_mutex_unlock(struct sys_mutex *mutex) return 0; } +/* provide a no-op implementation for zephyr/sys/sem.h */ + +struct sys_sem { +}; + +static inline int sys_sem_init(struct sys_sem *sem, unsigned int initial_count, + unsigned int limit) +{ + return 0; +} + +static inline int sys_sem_give(struct sys_sem *sem) +{ + return 0; +} + +static inline int sys_sem_take(struct sys_sem *sem, k_timeout_t timeout) +{ + return 0; +} + #endif diff --git a/src/schedule/zephyr_ll.c b/src/schedule/zephyr_ll.c index e2d02f12926b..654948e8377f 100644 --- a/src/schedule/zephyr_ll.c +++ b/src/schedule/zephyr_ll.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -40,10 +41,7 @@ struct zephyr_ll { struct zephyr_ll_pdata { bool run; bool freeing; -#ifndef CONFIG_SOF_USERSPACE_LL - struct k_sem sem_obj; -#endif - struct k_sem *sem; + struct sys_sem sem; }; static void zephyr_ll_lock(struct zephyr_ll *sch, uint32_t *flags) @@ -90,7 +88,7 @@ static void zephyr_ll_task_done(struct zephyr_ll *sch, * zephyr_ll_task_free() is trying to free this task. Complete * it and signal the semaphore to let the function proceed */ - k_sem_give(pdata->sem); + sys_sem_give(&pdata->sem); tr_info(&ll_tr, "task complete %p %pU", task, task->uid); tr_info(&ll_tr, "num_tasks %d total_num_tasks %ld", @@ -457,11 +455,7 @@ static int zephyr_ll_task_free(void *data, struct task *task) if (must_wait) /* Wait for up to 100 periods */ - k_sem_take(pdata->sem, K_USEC(LL_TIMER_PERIOD_US * 100)); - -#ifdef CONFIG_SOF_USERSPACE_LL - k_object_free(pdata->sem); -#endif + sys_sem_take(&pdata->sem, K_USEC(LL_TIMER_PERIOD_US * 100)); /* Protect against racing with schedule_task() */ zephyr_ll_lock(sch, &flags); @@ -567,12 +561,7 @@ int zephyr_ll_task_init(struct task *task, memset(pdata, 0, sizeof(*pdata)); -#ifdef CONFIG_SOF_USERSPACE_LL - pdata->sem = k_object_alloc(K_OBJ_SEM); -#else - pdata->sem = &pdata->sem_obj; -#endif - k_sem_init(pdata->sem, 0, 1); + sys_sem_init(&pdata->sem, 0, 1); task->priv_data = pdata; From 8aaa004b0b400220a78fa4de308c7f958e65287c Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Fri, 13 Feb 2026 18:51:10 +0200 Subject: [PATCH 014/106] schedule: zephyr_ll_user: move user accessible heap to common partition Move the user-accessible heap pointer to common partition defined in userspace_helper.h. Signed-off-by: Kai Vehmanen --- src/schedule/zephyr_ll_user.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/schedule/zephyr_ll_user.c b/src/schedule/zephyr_ll_user.c index f915fd506a4d..2837278fc299 100644 --- a/src/schedule/zephyr_ll_user.c +++ b/src/schedule/zephyr_ll_user.c @@ -25,9 +25,8 @@ struct zephyr_ll_mem_resources { static struct zephyr_ll_mem_resources ll_mem_resources; -K_APPMEM_PARTITION_DEFINE(ll_common); /* Heap allocator for LL scheduler memory (user accessible pointer) */ -K_APP_BMEM(ll_common) static struct k_heap *zephyr_ll_heap; +APP_TASK_DATA static struct k_heap *zephyr_ll_heap; static struct k_heap *zephyr_ll_heap_init(void) { @@ -64,11 +63,6 @@ static struct k_heap *zephyr_ll_heap_init(void) if (ret) k_panic(); - ret = k_mem_domain_add_partition(&ll_mem_resources.mem_domain, &ll_common); - tr_dbg(&ll_tr, "init ll common %p, ret %d", (void *)&ll_common, ret); - if (ret) - k_panic(); - return heap; } From 6d617ae588eccc6c9a9723bb3dacd20bf1bff399 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Mon, 16 Feb 2026 16:16:01 +0200 Subject: [PATCH 015/106] dma: dma_sg: allocate on specific heap Add a heap parameter to DMA scatter-gather allocation interface. This makes it possible to control how allocations are done for the DMA buffers. Signed-off-by: Kai Vehmanen --- posix/include/sof/lib/dma.h | 6 ++++-- src/audio/dai-legacy.c | 6 +++--- src/audio/dai-zephyr.c | 6 +++--- src/audio/host-legacy.c | 12 ++++++------ src/audio/host-zephyr.c | 12 ++++++------ src/ipc/ipc3/handler.c | 2 +- src/ipc/ipc3/host-page-table.c | 2 +- src/lib/dma.c | 13 ++++++++----- src/probe/probe.c | 4 ++-- src/trace/dma-trace.c | 2 +- zephyr/include/sof/lib/dma.h | 6 ++++-- 11 files changed, 39 insertions(+), 32 deletions(-) diff --git a/posix/include/sof/lib/dma.h b/posix/include/sof/lib/dma.h index 960cfd469215..cb829dcaa21a 100644 --- a/posix/include/sof/lib/dma.h +++ b/posix/include/sof/lib/dma.h @@ -35,6 +35,7 @@ struct comp_buffer; struct comp_dev; +struct k_heap; /** \addtogroup sof_dma_drivers DMA Drivers * DMA Drivers API specification. @@ -511,13 +512,14 @@ static inline void dma_sg_init(struct dma_sg_elem_array *ea) ea->elems = NULL; } -int dma_sg_alloc(struct dma_sg_elem_array *ea, +int dma_sg_alloc(struct k_heap *heap, + struct dma_sg_elem_array *ea, uint32_t flags, uint32_t direction, uint32_t buffer_count, uint32_t buffer_bytes, uintptr_t dma_buffer_addr, uintptr_t external_addr); -void dma_sg_free(struct dma_sg_elem_array *ea); +void dma_sg_free(struct k_heap *heap, struct dma_sg_elem_array *ea); /** * \brief Get the total size of SG buffer diff --git a/src/audio/dai-legacy.c b/src/audio/dai-legacy.c index 11179334e6fe..655fa94afb86 100644 --- a/src/audio/dai-legacy.c +++ b/src/audio/dai-legacy.c @@ -379,7 +379,7 @@ static int dai_playback_params(struct comp_dev *dev, uint32_t period_bytes, comp_info(dev, "fifo 0x%x", fifo); - err = dma_sg_alloc(&config->elem_array, SOF_MEM_FLAG_USER, + err = dma_sg_alloc(NULL, &config->elem_array, SOF_MEM_FLAG_USER, config->direction, period_count, period_bytes, @@ -444,7 +444,7 @@ static int dai_capture_params(struct comp_dev *dev, uint32_t period_bytes, comp_info(dev, "fifo 0x%x", fifo); - err = dma_sg_alloc(&config->elem_array, SOF_MEM_FLAG_USER, + err = dma_sg_alloc(NULL, &config->elem_array, SOF_MEM_FLAG_USER, config->direction, period_count, period_bytes, @@ -709,7 +709,7 @@ void dai_common_reset(struct dai_data *dd, struct comp_dev *dev) if (!dd->delayed_dma_stop) dai_dma_release(dd, dev); - dma_sg_free(&config->elem_array); + dma_sg_free(NULL, &config->elem_array); if (dd->dma_buffer) { buffer_free(dd->dma_buffer); diff --git a/src/audio/dai-zephyr.c b/src/audio/dai-zephyr.c index 6dda66899c26..04a1a319301e 100644 --- a/src/audio/dai-zephyr.c +++ b/src/audio/dai-zephyr.c @@ -843,7 +843,7 @@ static int dai_set_sg_config(struct dai_data *dd, struct comp_dev *dev, uint32_t } while (--max_block_count > 0); } - err = dma_sg_alloc(&config->elem_array, SOF_MEM_FLAG_USER, + err = dma_sg_alloc(NULL, &config->elem_array, SOF_MEM_FLAG_USER, config->direction, period_count, period_bytes, @@ -1124,7 +1124,7 @@ int dai_common_params(struct dai_data *dd, struct comp_dev *dev, if (err < 0) { buffer_free(dd->dma_buffer); dd->dma_buffer = NULL; - dma_sg_free(&config->elem_array); + dma_sg_free(NULL, &config->elem_array); rfree(dd->z_config); dd->z_config = NULL; } @@ -1255,7 +1255,7 @@ void dai_common_reset(struct dai_data *dd, struct comp_dev *dev) if (!dd->delayed_dma_stop) dai_dma_release(dd, dev); - dma_sg_free(&config->elem_array); + dma_sg_free(NULL, &config->elem_array); if (dd->z_config) { rfree(dd->z_config->head_block); rfree(dd->z_config); diff --git a/src/audio/host-legacy.c b/src/audio/host-legacy.c index 7f919dbd2eea..3d62e271f518 100644 --- a/src/audio/host-legacy.c +++ b/src/audio/host-legacy.c @@ -445,7 +445,7 @@ static int create_local_elems(struct host_data *hd, struct comp_dev *dev, uint32 elem_array = &hd->local.elem_array; /* config buffer will be used as proxy */ - err = dma_sg_alloc(&hd->config.elem_array, SOF_MEM_FLAG_USER, + err = dma_sg_alloc(NULL, &hd->config.elem_array, SOF_MEM_FLAG_USER, dir, 1, 0, 0, 0); if (err < 0) { comp_err(dev, "dma_sg_alloc() failed"); @@ -455,7 +455,7 @@ static int create_local_elems(struct host_data *hd, struct comp_dev *dev, uint32 elem_array = &hd->config.elem_array; } - err = dma_sg_alloc(elem_array, SOF_MEM_FLAG_USER, dir, buffer_count, + err = dma_sg_alloc(NULL, elem_array, SOF_MEM_FLAG_USER, dir, buffer_count, buffer_bytes, (uintptr_t)(audio_stream_get_addr(&hd->dma_buffer->stream)), 0); if (err < 0) { @@ -615,7 +615,7 @@ void host_common_free(struct host_data *hd) dma_put(hd->dma); ipc_msg_free(hd->msg); - dma_sg_free(&hd->config.elem_array); + dma_sg_free(NULL, &hd->config.elem_array); } static void host_free(struct comp_dev *dev) @@ -937,9 +937,9 @@ void host_common_reset(struct host_data *hd, uint16_t state) } /* free all DMA elements */ - dma_sg_free(&hd->host.elem_array); - dma_sg_free(&hd->local.elem_array); - dma_sg_free(&hd->config.elem_array); + dma_sg_free(NULL, &hd->host.elem_array); + dma_sg_free(NULL, &hd->local.elem_array); + dma_sg_free(NULL, &hd->config.elem_array); /* It's safe that cleaning out `hd->config` after `dma_sg_free` for config.elem_array */ memset(&hd->config, 0, sizeof(hd->config)); diff --git a/src/audio/host-zephyr.c b/src/audio/host-zephyr.c index 613b38bcd00f..96a8e49a7706 100644 --- a/src/audio/host-zephyr.c +++ b/src/audio/host-zephyr.c @@ -610,7 +610,7 @@ static int create_local_elems(struct host_data *hd, struct comp_dev *dev, elem_array = &hd->local.elem_array; /* config buffer will be used as proxy */ - err = dma_sg_alloc(&hd->config.elem_array, SOF_MEM_FLAG_USER, + err = dma_sg_alloc(NULL, &hd->config.elem_array, SOF_MEM_FLAG_USER, dir, 1, 0, 0, 0); if (err < 0) { comp_err(dev, "dma_sg_alloc() failed"); @@ -620,7 +620,7 @@ static int create_local_elems(struct host_data *hd, struct comp_dev *dev, elem_array = &hd->config.elem_array; } - err = dma_sg_alloc(elem_array, SOF_MEM_FLAG_USER, dir, buffer_count, + err = dma_sg_alloc(NULL, elem_array, SOF_MEM_FLAG_USER, dir, buffer_count, buffer_bytes, (uintptr_t)audio_stream_get_addr(&hd->dma_buffer->stream), 0); if (err < 0) { @@ -787,7 +787,7 @@ __cold void host_common_free(struct host_data *hd) sof_dma_put(hd->dma); ipc_msg_free(hd->msg); - dma_sg_free(&hd->config.elem_array); + dma_sg_free(NULL, &hd->config.elem_array); } __cold static void host_free(struct comp_dev *dev) @@ -1177,9 +1177,9 @@ void host_common_reset(struct host_data *hd, uint16_t state) } /* free all DMA elements */ - dma_sg_free(&hd->host.elem_array); - dma_sg_free(&hd->local.elem_array); - dma_sg_free(&hd->config.elem_array); + dma_sg_free(NULL, &hd->host.elem_array); + dma_sg_free(NULL, &hd->local.elem_array); + dma_sg_free(NULL, &hd->config.elem_array); /* free DMA buffer */ if (hd->dma_buffer) { diff --git a/src/ipc/ipc3/handler.c b/src/ipc/ipc3/handler.c index 80ddb2225e43..5bd5293c61b7 100644 --- a/src/ipc/ipc3/handler.c +++ b/src/ipc/ipc3/handler.c @@ -878,7 +878,7 @@ static int ipc_dma_trace_config(uint32_t header) error: #if CONFIG_HOST_PTABLE - dma_sg_free(&elem_array); + dma_sg_free(NULL, &elem_array); processing_error: #endif diff --git a/src/ipc/ipc3/host-page-table.c b/src/ipc/ipc3/host-page-table.c index 7a3da31caa82..997ee6683352 100644 --- a/src/ipc/ipc3/host-page-table.c +++ b/src/ipc/ipc3/host-page-table.c @@ -239,6 +239,6 @@ int ipc_process_host_buffer(struct ipc *ipc, return 0; error: - dma_sg_free(elem_array); + dma_sg_free(NULL, elem_array); return err; } diff --git a/src/lib/dma.c b/src/lib/dma.c index 608ee091ac25..70ac5c975058 100644 --- a/src/lib/dma.c +++ b/src/lib/dma.c @@ -285,7 +285,8 @@ void dma_put(struct dma *dma) } #endif -int dma_sg_alloc(struct dma_sg_elem_array *elem_array, +int dma_sg_alloc(struct k_heap *heap, + struct dma_sg_elem_array *elem_array, uint32_t flags, uint32_t direction, uint32_t buffer_count, uint32_t buffer_bytes, @@ -293,11 +294,13 @@ int dma_sg_alloc(struct dma_sg_elem_array *elem_array, { int i; - elem_array->elems = rzalloc(SOF_MEM_FLAG_USER, - sizeof(struct dma_sg_elem) * buffer_count); + elem_array->elems = sof_heap_alloc(heap, SOF_MEM_FLAG_USER, + sizeof(struct dma_sg_elem) * buffer_count, 0); if (!elem_array->elems) return -ENOMEM; + memset(elem_array->elems, 0, sizeof(struct dma_sg_elem) * buffer_count); + for (i = 0; i < buffer_count; i++) { elem_array->elems[i].size = buffer_bytes; // TODO: may count offsets once @@ -319,9 +322,9 @@ int dma_sg_alloc(struct dma_sg_elem_array *elem_array, return 0; } -void dma_sg_free(struct dma_sg_elem_array *elem_array) +void dma_sg_free(struct k_heap *heap, struct dma_sg_elem_array *elem_array) { - rfree(elem_array->elems); + sof_heap_free(heap, elem_array->elems); dma_sg_init(elem_array); } diff --git a/src/probe/probe.c b/src/probe/probe.c index f15ee84f7daf..088a2eb1773b 100644 --- a/src/probe/probe.c +++ b/src/probe/probe.c @@ -176,7 +176,7 @@ static int probe_dma_init(struct probe_dma_ext *dma, uint32_t direction) dma->config.dest_width = sizeof(uint32_t); dma->config.cyclic = 0; - err = dma_sg_alloc(&dma->config.elem_array, SOF_MEM_FLAG_USER, + err = dma_sg_alloc(NULL, &dma->config.elem_array, SOF_MEM_FLAG_USER, dma->config.direction, elem_num, elem_size, elem_addr, 0); if (err < 0) return err; @@ -255,7 +255,7 @@ static int probe_dma_init(struct probe_dma_ext *dma, uint32_t direction) static int probe_dma_deinit(struct probe_dma_ext *dma) { int err = 0; - dma_sg_free(&dma->config.elem_array); + dma_sg_free(NULL, &dma->config.elem_array); #if CONFIG_ZEPHYR_NATIVE_DRIVERS err = dma_stop(dma->dc.dmac->z_dev, dma->dc.chan->index); #else diff --git a/src/trace/dma-trace.c b/src/trace/dma-trace.c index 0523a362e397..554204ac5c70 100644 --- a/src/trace/dma-trace.c +++ b/src/trace/dma-trace.c @@ -407,7 +407,7 @@ void dma_trace_disable(struct dma_trace_data *d) #if (CONFIG_HOST_PTABLE) /* Free up the host SG if it is set */ if (d->host_size) { - dma_sg_free(&d->config.elem_array); + dma_sg_free(NULL, &d->config.elem_array); d->host_size = 0; } #endif diff --git a/zephyr/include/sof/lib/dma.h b/zephyr/include/sof/lib/dma.h index b13f3c25221b..e85f4b4d2e80 100644 --- a/zephyr/include/sof/lib/dma.h +++ b/zephyr/include/sof/lib/dma.h @@ -34,6 +34,7 @@ struct comp_buffer; struct comp_dev; +struct k_heap; /** \addtogroup sof_dma_drivers DMA Drivers * SOF DMA Drivers API specification (deprecated interface, to be @@ -291,13 +292,14 @@ static inline void dma_sg_init(struct dma_sg_elem_array *ea) ea->elems = NULL; } -int dma_sg_alloc(struct dma_sg_elem_array *ea, +int dma_sg_alloc(struct k_heap *heap, + struct dma_sg_elem_array *ea, uint32_t flags, uint32_t direction, uint32_t buffer_count, uint32_t buffer_bytes, uintptr_t dma_buffer_addr, uintptr_t external_addr); -void dma_sg_free(struct dma_sg_elem_array *ea); +void dma_sg_free(struct k_heap *heap, struct dma_sg_elem_array *ea); /** * \brief Get the total size of SG buffer From a9eac57403f4846354f7b8fec917a94a6c960037 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Mon, 16 Feb 2026 19:21:57 +0200 Subject: [PATCH 016/106] buffer: extend ability to allocate on specific heap to all functions Continue the work in commit 9567234c3b6b ("buffer: allocate on specific heap") and add ability to specify the heap to all buffer interface functions. Signed-off-by: Kai Vehmanen --- src/audio/buffers/comp_buffer.c | 54 ++++++++++++++------------------- src/include/sof/audio/buffer.h | 3 ++ 2 files changed, 26 insertions(+), 31 deletions(-) diff --git a/src/audio/buffers/comp_buffer.c b/src/audio/buffers/comp_buffer.c index d6562b8d821f..6804bbf71f04 100644 --- a/src/audio/buffers/comp_buffer.c +++ b/src/audio/buffers/comp_buffer.c @@ -161,7 +161,7 @@ static void comp_buffer_free(struct sof_audio_buffer *audio_buffer) struct k_heap *heap = buffer->audio_buffer.heap; - rfree(buffer->stream.addr); + sof_heap_free(heap, buffer->stream.addr); sof_heap_free(heap, buffer); if (heap) { struct dp_heap_user *mod_heap_user = container_of(heap, struct dp_heap_user, heap); @@ -218,6 +218,7 @@ static struct comp_buffer *buffer_alloc_struct(struct k_heap *heap, memset(buffer, 0, sizeof(*buffer)); + buffer->heap = heap; buffer->flags = flags; /* Force channels to 2 for init to prevent bad call to clz in buffer_init_stream */ buffer->stream.runtime_stream_params.channels = 2; @@ -254,7 +255,7 @@ struct comp_buffer *buffer_alloc(struct k_heap *heap, size_t size, uint32_t flag return NULL; } - stream_addr = rballoc_align(flags, size, align); + stream_addr = sof_heap_alloc(heap, flags, size, align); if (!stream_addr) { tr_err(&buffer_tr, "could not alloc size = %zu bytes of flags = 0x%x", size, flags); @@ -264,9 +265,11 @@ struct comp_buffer *buffer_alloc(struct k_heap *heap, size_t size, uint32_t flag buffer = buffer_alloc_struct(heap, stream_addr, size, flags, is_shared); if (!buffer) { tr_err(&buffer_tr, "could not alloc buffer structure"); - rfree(stream_addr); + sof_heap_free(heap, stream_addr); } + buffer->heap = heap; + return buffer; } @@ -292,7 +295,7 @@ struct comp_buffer *buffer_alloc_range(struct k_heap *heap, size_t preferred_siz preferred_size += minimum_size - preferred_size % minimum_size; for (size = preferred_size; size >= minimum_size; size -= minimum_size) { - stream_addr = rballoc_align(flags, size, align); + stream_addr = sof_heap_alloc(heap, flags, size, align); if (stream_addr) break; } @@ -308,9 +311,11 @@ struct comp_buffer *buffer_alloc_range(struct k_heap *heap, size_t preferred_siz buffer = buffer_alloc_struct(heap, stream_addr, size, flags, is_shared); if (!buffer) { tr_err(&buffer_tr, "could not alloc buffer structure"); - rfree(stream_addr); + sof_heap_free(heap, stream_addr); } + buffer->heap = heap; + return buffer; } @@ -341,14 +346,8 @@ int buffer_set_size(struct comp_buffer *buffer, uint32_t size, uint32_t alignmen if (size == audio_stream_get_size(&buffer->stream)) return 0; - if (!alignment) - new_ptr = rbrealloc(audio_stream_get_addr(&buffer->stream), - buffer->flags | SOF_MEM_FLAG_NO_COPY, - size, audio_stream_get_size(&buffer->stream)); - else - new_ptr = rbrealloc_align(audio_stream_get_addr(&buffer->stream), - buffer->flags | SOF_MEM_FLAG_NO_COPY, size, - audio_stream_get_size(&buffer->stream), alignment); + new_ptr = sof_heap_alloc(buffer->heap, buffer->flags, size, alignment); + /* we couldn't allocate bigger chunk */ if (!new_ptr && size > audio_stream_get_size(&buffer->stream)) { buf_err(buffer, "resize can't alloc %u bytes of flags 0x%x", @@ -357,8 +356,10 @@ int buffer_set_size(struct comp_buffer *buffer, uint32_t size, uint32_t alignmen } /* use bigger chunk, else just use the old chunk but set smaller */ - if (new_ptr) + if (new_ptr) { + sof_heap_free(buffer->heap, audio_stream_get_addr(&buffer->stream)); buffer->stream.addr = new_ptr; + } buffer_init_stream(buffer, size); @@ -389,22 +390,11 @@ int buffer_set_size_range(struct comp_buffer *buffer, size_t preferred_size, siz if (preferred_size == actual_size) return 0; - if (!alignment) { - for (new_size = preferred_size; new_size >= minimum_size; - new_size -= minimum_size) { - new_ptr = rbrealloc(ptr, buffer->flags | SOF_MEM_FLAG_NO_COPY, - new_size, actual_size); - if (new_ptr) - break; - } - } else { - for (new_size = preferred_size; new_size >= minimum_size; - new_size -= minimum_size) { - new_ptr = rbrealloc_align(ptr, buffer->flags | SOF_MEM_FLAG_NO_COPY, - new_size, actual_size, alignment); - if (new_ptr) - break; - } + for (new_size = preferred_size; new_size >= minimum_size; + new_size -= minimum_size) { + new_ptr = sof_heap_alloc(buffer->heap, buffer->flags, new_size, alignment); + if (new_ptr) + break; } /* we couldn't allocate bigger chunk */ @@ -415,8 +405,10 @@ int buffer_set_size_range(struct comp_buffer *buffer, size_t preferred_size, siz } /* use bigger chunk, else just use the old chunk but set smaller */ - if (new_ptr) + if (new_ptr) { + sof_heap_free(buffer->heap, audio_stream_get_addr(&buffer->stream)); buffer->stream.addr = new_ptr; + } buffer_init_stream(buffer, new_size); diff --git a/src/include/sof/audio/buffer.h b/src/include/sof/audio/buffer.h index 91c09ef2e510..5d5e64e8043d 100644 --- a/src/include/sof/audio/buffer.h +++ b/src/include/sof/audio/buffer.h @@ -33,6 +33,7 @@ #include struct comp_dev; +struct k_heap; /** \name Trace macros * @{ @@ -148,6 +149,8 @@ struct comp_buffer { /* list of buffers, to be used i.e. in raw data processing mode*/ struct list_item buffers_list; + + struct k_heap *heap; }; /* From 7530c7164e15e9ae442f31a8a64dece23e3cc467 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Tue, 14 Apr 2026 13:16:15 +0300 Subject: [PATCH 017/106] app: overlays: ptl: add ll_usespace_overlay.conf Add an overlay for Intel 'ptl' target that allows to build SOF with all audio pipeline code running in Zephyr user-sapce. Signed-off-by: Kai Vehmanen --- app/overlays/ptl/ll_userspace_overlay.conf | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 app/overlays/ptl/ll_userspace_overlay.conf diff --git a/app/overlays/ptl/ll_userspace_overlay.conf b/app/overlays/ptl/ll_userspace_overlay.conf new file mode 100644 index 000000000000..6dcaa859a47a --- /dev/null +++ b/app/overlays/ptl/ll_userspace_overlay.conf @@ -0,0 +1,22 @@ +CONFIG_SOF_USERSPACE_LL=y + +# temporary (but for now mandatory) settings +CONFIG_SOF_ZEPHYR_USERSPACE_MODULE_HEAP_SIZE=16384 + +# make the drivers work in user-space +CONFIG_SOF_USERSPACE_INTERFACE_DMA=y +CONFIG_DAI_USERSPACE=y + +# disable features that don't work in user-space (at least yet) +CONFIG_COLD_STORE_EXECUTE_DEBUG=n +CONFIG_CROSS_CORE_STREAM=n +CONFIG_INTEL_ADSP_MIC_PRIVACY=n +CONFIG_XRUN_NOTIFICATIONS_ENABLE=n +CONFIG_SOF_BOOT_TEST_ALLOWED=n +CONFIG_SOF_TELEMETRY_PERFORMANCE_MEASUREMENTS=n +CONFIG_SOF_TELEMETRY_IO_PERFORMANCE_MEASUREMENTS=n + +# disable llext (hits privilege issues in user-space now) +CONFIG_LLEXT_STORAGE_WRITABLE=n +CONFIG_LLEXT_EXPERIMENTAL=n +CONFIG_MODULES=n From 8adac7fd9b6907c5ed8eab8a3b5b16a5b07bf963 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Fri, 27 Mar 2026 13:05:38 +0200 Subject: [PATCH 018/106] (---section: user LL infra STOP) From 5c30af4d0f19649356770927c05b1250fb7ec3b5 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 19 Feb 2026 14:50:56 +0200 Subject: [PATCH 019/106] (---section host-zephyr START) From 02e4706c5efd93414b2703b35f966108bd9ef90d Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 19 Feb 2026 14:37:07 +0200 Subject: [PATCH 020/106] zephyr: userspace: sof_dma: allow circular SG lists Allow a non-null pointer at the end of the DMA transfer block list, if and only if it points to the first entry in the block list. The SOF DAI module sets the DMA transfers blocks like this and this change is required to use DAI module from user-space. Signed-off-by: Kai Vehmanen --- zephyr/syscall/sof_dma.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/zephyr/syscall/sof_dma.c b/zephyr/syscall/sof_dma.c index ed69ffc78423..11ee8156f7ff 100644 --- a/zephyr/syscall/sof_dma.c +++ b/zephyr/syscall/sof_dma.c @@ -119,9 +119,15 @@ static inline struct dma_block_config *deep_copy_dma_blk_cfg_list(struct dma_con for (user_next = cfg->head_block, kern_next = kern_cfg; user_next; - user_next = user_next->next_block, kern_next++) { - if (++i > cfg->block_count) - goto err; + user_next = user_next->next_block, kern_next++, i++) { + if (i == cfg->block_count) { + /* last block can point to first one */ + if (user_next != cfg->head_block) + goto err; + + kern_prev->next_block = kern_cfg; + break; + } if (k_usermode_from_copy(kern_next, user_next, sizeof(*kern_next))) goto err; From 44eed128454c4ed7c065e53e9bd9c61b2bdcc3e0 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Fri, 13 Feb 2026 18:54:45 +0200 Subject: [PATCH 021/106] zephyr: lib: dma: make DMA platform data available to user-space The platform data descriptions need to be accessible to all threads. These are e.g. used when setting up host/DAI copiers and they need platform DMA properties. Signed-off-by: Kai Vehmanen --- zephyr/lib/dma.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zephyr/lib/dma.c b/zephyr/lib/dma.c index 7452459f8b0a..0b8987ed65d0 100644 --- a/zephyr/lib/dma.c +++ b/zephyr/lib/dma.c @@ -15,12 +15,13 @@ #include #include #include +#include #include #define DW_DMA_BUFFER_PERIOD_COUNT 0x4 #define HDA_DMA_BUFFER_PERIOD_COUNT 4 -SHARED_DATA struct sof_dma dma[] = { +APP_TASK_DATA SHARED_DATA struct sof_dma dma[] = { #if DT_NODE_HAS_STATUS(DT_NODELABEL(lpgpdma0), okay) { /* Low Power GP DMAC 0 */ .plat_data = { From 18748e85fd1df614b760762cd40fda1c56639351 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Fri, 13 Feb 2026 18:53:39 +0200 Subject: [PATCH 022/106] audio: host-zephyr: select heap when allocating host buffers Use separate heaps depending whether host copier is run in user or kernel space. Signed-off-by: Kai Vehmanen --- src/audio/host-zephyr.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/audio/host-zephyr.c b/src/audio/host-zephyr.c index 96a8e49a7706..9698e0928e76 100644 --- a/src/audio/host-zephyr.c +++ b/src/audio/host-zephyr.c @@ -956,7 +956,8 @@ int host_common_params(struct host_data *hd, struct comp_dev *dev, } } else { /* allocate not shared buffer */ - hd->dma_buffer = buffer_alloc_range(NULL, buffer_size_preferred, buffer_size, + hd->dma_buffer = buffer_alloc_range(sof_sys_user_heap_get(), + buffer_size_preferred, buffer_size, SOF_MEM_FLAG_USER | SOF_MEM_FLAG_DMA, addr_align, BUFFER_USAGE_NOT_SHARED); if (!hd->dma_buffer) { From 011ace6ce331368676cc930e6af0b909da1f66bb Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Mon, 16 Feb 2026 19:27:56 +0200 Subject: [PATCH 023/106] audio: host-zephyr: rework calls to DMA driver, remove channel pointer For historical reasons, host-zephyr has somewhat complicated code to manage the DMA channel instance information. When a DMA channel is allocated, a pointer to the system DMA channel table is acquired and some additional information is stored per channel. This is however redundant as the only piece of information actually needed is the channel index. Simplify the code by not storing the channel pointer anymore, but rather just store the channel index and use that in all calls to the DMA driver. Signed-off-by: Kai Vehmanen --- src/audio/copier/host_copier.h | 3 ++- src/audio/host-zephyr.c | 48 +++++++++++++++------------------- 2 files changed, 23 insertions(+), 28 deletions(-) diff --git a/src/audio/copier/host_copier.h b/src/audio/copier/host_copier.h index 9db9028a54d7..2bc8904e1258 100644 --- a/src/audio/copier/host_copier.h +++ b/src/audio/copier/host_copier.h @@ -54,10 +54,11 @@ struct host_data { /* local DMA config */ #if CONFIG_ZEPHYR_NATIVE_DRIVERS struct sof_dma *dma; + int chan_index; #else struct dma *dma; -#endif struct dma_chan_data *chan; +#endif struct dma_sg_config config; #ifdef __ZEPHYR__ struct dma_config z_config; diff --git a/src/audio/host-zephyr.c b/src/audio/host-zephyr.c index 9698e0928e76..3b4692a75fab 100644 --- a/src/audio/host-zephyr.c +++ b/src/audio/host-zephyr.c @@ -84,7 +84,7 @@ static int host_dma_set_config_and_copy(struct host_data *hd, struct comp_dev *d local_elem->size = bytes; /* reconfigure transfer */ - ret = sof_dma_config(hd->chan->dma, hd->chan->index, &hd->z_config); + ret = sof_dma_config(hd->dma, hd->chan_index, &hd->z_config); if (ret < 0) { comp_err(dev, "dma_config() failed, ret = %d", ret); @@ -93,7 +93,7 @@ static int host_dma_set_config_and_copy(struct host_data *hd, struct comp_dev *d cb(dev, bytes); - ret = sof_dma_reload(hd->chan->dma, hd->chan->index, bytes); + ret = sof_dma_reload(hd->dma, hd->chan_index, bytes); if (ret < 0) { comp_err(dev, "dma_copy() failed, ret = %d", ret); @@ -223,7 +223,7 @@ static int host_copy_one_shot(struct host_data *hd, struct comp_dev *dev, copy_c hd->z_config.head_block->block_size = local_elem->size; /* reconfigure transfer */ - ret = sof_dma_config(hd->chan->dma, hd->chan->index, &hd->z_config); + ret = sof_dma_config(hd->dma, hd->chan_index, &hd->z_config); if (ret < 0) { comp_err(dev, "dma_config() failed, ret = %u", ret); return ret; @@ -231,7 +231,7 @@ static int host_copy_one_shot(struct host_data *hd, struct comp_dev *dev, copy_c cb(dev, copy_bytes); - ret = sof_dma_reload(hd->chan->dma, hd->chan->index, copy_bytes); + ret = sof_dma_reload(hd->dma, hd->chan_index, copy_bytes); if (ret < 0) comp_err(dev, "dma_copy() failed, ret = %u", ret); @@ -369,7 +369,7 @@ static void host_dma_cb(struct comp_dev *dev, size_t bytes) /* get status from dma and check for xrun */ static int host_get_status(struct comp_dev *dev, struct host_data *hd, struct dma_status *stat) { - int ret = sof_dma_get_status(hd->chan->dma, hd->chan->index, stat); + int ret = sof_dma_get_status(hd->dma, hd->chan_index, stat); #if CONFIG_XRUN_NOTIFICATIONS_ENABLE if (ret == -EPIPE && !hd->xrun_notification_sent) { hd->xrun_notification_sent = send_copier_gateway_xrun_notif_msg @@ -556,7 +556,7 @@ static int host_copy_normal(struct host_data *hd, struct comp_dev *dev, copy_cal if (!copy_bytes) { if (hd->partial_size != 0) { if (stream_sync(hd, dev)) { - ret = sof_dma_reload(hd->chan->dma, hd->chan->index, + ret = sof_dma_reload(hd->dma, hd->chan_index, hd->partial_size); if (ret < 0) comp_err(dev, "dma_reload() failed, ret = %u", ret); @@ -583,7 +583,7 @@ static int host_copy_normal(struct host_data *hd, struct comp_dev *dev, copy_cal hd->dma_buffer_size - hd->partial_size <= (2 + threshold) * hd->period_bytes) { if (stream_sync(hd, dev)) { - ret = sof_dma_reload(hd->chan->dma, hd->chan->index, + ret = sof_dma_reload(hd->dma, hd->chan_index, hd->partial_size); if (ret < 0) comp_err(dev, "dma_reload() failed, ret = %u", ret); @@ -651,7 +651,7 @@ int host_common_trigger(struct host_data *hd, struct comp_dev *dev, int cmd) if (cmd != COMP_TRIGGER_START && hd->copy_type == COMP_COPY_ONE_SHOT) return ret; - if (!hd->chan) { + if (hd->chan_index == -1) { comp_err(dev, "no dma channel configured"); return -EINVAL; } @@ -659,14 +659,14 @@ int host_common_trigger(struct host_data *hd, struct comp_dev *dev, int cmd) switch (cmd) { case COMP_TRIGGER_START: hd->partial_size = 0; - ret = sof_dma_start(hd->chan->dma, hd->chan->index); + ret = sof_dma_start(hd->dma, hd->chan_index); if (ret < 0) comp_err(dev, "dma_start() failed, ret = %u", ret); break; case COMP_TRIGGER_STOP: case COMP_TRIGGER_XRUN: - ret = sof_dma_stop(hd->chan->dma, hd->chan->index); + ret = sof_dma_stop(hd->dma, hd->chan_index); if (ret < 0) comp_err(dev, "dma stop failed: %d", ret); @@ -726,7 +726,7 @@ __cold int host_common_new(struct host_data *hd, struct comp_dev *dev, sof_dma_put(hd->dma); return -ENOMEM; } - hd->chan = NULL; + hd->chan_index = -1; hd->copy_type = COMP_COPY_NORMAL; return 0; @@ -865,7 +865,7 @@ int host_common_params(struct host_data *hd, struct comp_dev *dev, uint32_t buffer_size_preferred; uint32_t addr_align; uint32_t align; - int i, channel, err; + int i, err; bool is_scheduling_source = dev == dev->pipeline->sched_comp; uint32_t round_up_size; @@ -1002,22 +1002,16 @@ int host_common_params(struct host_data *hd, struct comp_dev *dev, /* get DMA channel from DMAC * note: stream_tag is ignored by dw-dma */ - channel = sof_dma_request_channel(hd->dma, hda_chan); - if (channel < 0) { + hd->chan_index = sof_dma_request_channel(hd->dma, hda_chan); + if (hd->chan_index < 0) { comp_err(dev, "requested channel %d is busy", hda_chan); return -ENODEV; } - hd->chan = &hd->dma->chan[channel]; uint32_t buffer_addr = 0; uint32_t buffer_bytes = 0; uint32_t addr; - hd->chan->direction = config->direction; - hd->chan->desc_count = config->elem_array.count; - hd->chan->is_scheduling_source = config->is_scheduling_source; - hd->chan->period = config->period; - memset(dma_cfg, 0, sizeof(*dma_cfg)); dma_block_cfg = rzalloc(SOF_MEM_FLAG_USER, @@ -1064,7 +1058,7 @@ int host_common_params(struct host_data *hd, struct comp_dev *dev, break; } - err = sof_dma_config(hd->chan->dma, hd->chan->index, dma_cfg); + err = sof_dma_config(hd->dma, hd->chan_index, dma_cfg); if (err < 0) { comp_err(dev, "dma_config() failed"); goto err_free_block_cfg; @@ -1112,8 +1106,8 @@ int host_common_params(struct host_data *hd, struct comp_dev *dev, dma_cfg->head_block = NULL; rfree(dma_block_cfg); err_release_channel: - sof_dma_release_channel(hd->dma, hd->chan->index); - hd->chan = NULL; + sof_dma_release_channel(hd->dma, hd->chan_index); + hd->chan_index = -1; return err; } @@ -1171,10 +1165,10 @@ static int host_position(struct comp_dev *dev, void host_common_reset(struct host_data *hd, uint16_t state) { - if (hd->chan) { - sof_dma_stop(hd->chan->dma, hd->chan->index); - sof_dma_release_channel(hd->dma, hd->chan->index); - hd->chan = NULL; + if (hd->chan_index != -1) { + sof_dma_stop(hd->dma, hd->chan_index); + sof_dma_release_channel(hd->dma, hd->chan_index); + hd->chan_index = -1; } /* free all DMA elements */ From e96c5e70449597c1e6baa18016c0029e49e239db Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Mon, 16 Feb 2026 19:41:35 +0200 Subject: [PATCH 024/106] audio: host-zephyr: pass component heap to dma_sg_alloc Make sure we use the same heap to allocate DMA SG buffers as we use for other component resources. Signed-off-by: Kai Vehmanen --- src/audio/host-zephyr.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/audio/host-zephyr.c b/src/audio/host-zephyr.c index 3b4692a75fab..8a19ec9941a3 100644 --- a/src/audio/host-zephyr.c +++ b/src/audio/host-zephyr.c @@ -610,7 +610,7 @@ static int create_local_elems(struct host_data *hd, struct comp_dev *dev, elem_array = &hd->local.elem_array; /* config buffer will be used as proxy */ - err = dma_sg_alloc(NULL, &hd->config.elem_array, SOF_MEM_FLAG_USER, + err = dma_sg_alloc(hd->heap, &hd->config.elem_array, SOF_MEM_FLAG_USER, dir, 1, 0, 0, 0); if (err < 0) { comp_err(dev, "dma_sg_alloc() failed"); @@ -620,7 +620,7 @@ static int create_local_elems(struct host_data *hd, struct comp_dev *dev, elem_array = &hd->config.elem_array; } - err = dma_sg_alloc(NULL, elem_array, SOF_MEM_FLAG_USER, dir, buffer_count, + err = dma_sg_alloc(hd->heap, elem_array, SOF_MEM_FLAG_USER, dir, buffer_count, buffer_bytes, (uintptr_t)audio_stream_get_addr(&hd->dma_buffer->stream), 0); if (err < 0) { @@ -787,7 +787,7 @@ __cold void host_common_free(struct host_data *hd) sof_dma_put(hd->dma); ipc_msg_free(hd->msg); - dma_sg_free(NULL, &hd->config.elem_array); + dma_sg_free(hd->heap, &hd->config.elem_array); } __cold static void host_free(struct comp_dev *dev) @@ -1172,9 +1172,9 @@ void host_common_reset(struct host_data *hd, uint16_t state) } /* free all DMA elements */ - dma_sg_free(NULL, &hd->host.elem_array); - dma_sg_free(NULL, &hd->local.elem_array); - dma_sg_free(NULL, &hd->config.elem_array); + dma_sg_free(hd->heap, &hd->host.elem_array); + dma_sg_free(hd->heap, &hd->local.elem_array); + dma_sg_free(hd->heap, &hd->config.elem_array); /* free DMA buffer */ if (hd->dma_buffer) { From 1ec47dc924fc705a7b6e0aed13a3ddfecf10d9ef Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Mon, 16 Feb 2026 19:53:13 +0200 Subject: [PATCH 025/106] alloc.h: remove rbrelloac() and rbrealloc_align() These interfaces are no longer used anywhere, so they can be safely removed. Signed-off-by: Kai Vehmanen --- posix/include/rtos/alloc.h | 23 ----------------------- test/cmocka/src/common_mocks.c | 10 ---------- zephyr/include/rtos/alloc.h | 23 ----------------------- 3 files changed, 56 deletions(-) diff --git a/posix/include/rtos/alloc.h b/posix/include/rtos/alloc.h index 60dc951876e0..84751ef9f1f4 100644 --- a/posix/include/rtos/alloc.h +++ b/posix/include/rtos/alloc.h @@ -97,29 +97,6 @@ static inline void *rballoc(uint32_t flags, size_t bytes) return rballoc_align(flags, bytes, PLATFORM_DCACHE_ALIGN); } -/** - * Changes size of the memory block allocated. - * @param ptr Address of the block to resize. - * @param flags Flags, see SOF_MEM_FLAG_... - * @param bytes New size in bytes. - * @param old_bytes Old size in bytes. - * @param alignment Alignment in bytes. - * @return Pointer to the resized memory of NULL if failed. - */ -void *rbrealloc_align(void *ptr, uint32_t flags, size_t bytes, - size_t old_bytes, uint32_t alignment); - -/** - * Similar to rballoc_align(), returns resized buffer aligned to - * PLATFORM_DCACHE_ALIGN. - */ -static inline void *rbrealloc(void *ptr, uint32_t flags, - size_t bytes, size_t old_bytes) -{ - return rbrealloc_align(ptr, flags, bytes, old_bytes, - PLATFORM_DCACHE_ALIGN); -} - /** * Frees the memory block. * @param ptr Pointer to the memory block. diff --git a/test/cmocka/src/common_mocks.c b/test/cmocka/src/common_mocks.c index 60b6215c4cd5..16fee8f2ff48 100644 --- a/test/cmocka/src/common_mocks.c +++ b/test/cmocka/src/common_mocks.c @@ -59,16 +59,6 @@ void WEAK *rzalloc(uint32_t flags, return calloc(bytes, 1); } -void WEAK *rbrealloc_align(void *ptr, uint32_t flags, - size_t bytes, size_t old_bytes, uint32_t alignment) -{ - (void)flags; - (void)old_bytes; - (void)alignment; - - return realloc(ptr, bytes); -} - void WEAK *rmalloc_align(uint32_t flags, size_t bytes, uint32_t alignment) { (void)flags; diff --git a/zephyr/include/rtos/alloc.h b/zephyr/include/rtos/alloc.h index d261165e786b..f51ef77554be 100644 --- a/zephyr/include/rtos/alloc.h +++ b/zephyr/include/rtos/alloc.h @@ -88,29 +88,6 @@ static inline void *rballoc(uint32_t flags, size_t bytes) return rballoc_align(flags, bytes, PLATFORM_DCACHE_ALIGN); } -/** - * Changes size of the memory block allocated. - * @param ptr Address of the block to resize. - * @param flags Flags, see SOF_MEM_FLAG_... - * @param bytes New size in bytes. - * @param old_bytes Old size in bytes. - * @param alignment Alignment in bytes. - * @return Pointer to the resized memory of NULL if failed. - */ -void *rbrealloc_align(void *ptr, uint32_t flags, size_t bytes, - size_t old_bytes, uint32_t alignment); - -/** - * Similar to rballoc_align(), returns resized buffer aligned to - * PLATFORM_DCACHE_ALIGN. - */ -static inline void *rbrealloc(void *ptr, uint32_t flags, - size_t bytes, size_t old_bytes) -{ - return rbrealloc_align(ptr, flags, bytes, old_bytes, - PLATFORM_DCACHE_ALIGN); -} - /** * Frees the memory block. * @param ptr Pointer to the memory block. From 1b437a55c2aa92fe224c544997a6e92aa1eaa3eb Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Mon, 16 Feb 2026 19:56:52 +0200 Subject: [PATCH 026/106] audio: host-zephyr: ensure host data heap is set Force to use user-space LL heap in host_common_new() if CONFIG_SOF_USERSPACE_LL is set. Signed-off-by: Kai Vehmanen --- src/audio/host-zephyr.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/audio/host-zephyr.c b/src/audio/host-zephyr.c index 8a19ec9941a3..351cd379e3c2 100644 --- a/src/audio/host-zephyr.c +++ b/src/audio/host-zephyr.c @@ -729,6 +729,15 @@ __cold int host_common_new(struct host_data *hd, struct comp_dev *dev, hd->chan_index = -1; hd->copy_type = COMP_COPY_NORMAL; +#ifdef CONFIG_SOF_USERSPACE_LL + /* + * copier_host_create() uses mod_zalloc() to allocate + * the 'hd' host data object and does not set hd->heap. + * If LL is run in user-space, assign the 'heap' here. + */ + hd->heap = zephyr_ll_user_heap(); +#endif + return 0; } From ade66ae815784838ae02b1ac55e78b1745682266 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Fri, 10 Apr 2026 14:55:47 +0300 Subject: [PATCH 027/106] audio: host-zephyr: make component usable from user-space Ensure component heap is correctly passed whenever memory is allocated in the component. This allows to run the component both in kernel and user space. Signed-off-by: Kai Vehmanen --- src/audio/copier/host_copier.h | 3 +++ src/audio/host-zephyr.c | 28 +++++++++++++++++++--------- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/audio/copier/host_copier.h b/src/audio/copier/host_copier.h index 2bc8904e1258..ad16c7ac13de 100644 --- a/src/audio/copier/host_copier.h +++ b/src/audio/copier/host_copier.h @@ -117,9 +117,12 @@ struct host_data { uint64_t next_sync; uint64_t period_in_cycles; #endif + #ifdef CONFIG_SOF_TELEMETRY_IO_PERFORMANCE_MEASUREMENTS struct io_perf_data_item *io_perf_host_byte_count; #endif + + struct k_heap *heap; }; int host_common_new(struct host_data *hd, struct comp_dev *dev, diff --git a/src/audio/host-zephyr.c b/src/audio/host-zephyr.c index 351cd379e3c2..f59aa38e3f58 100644 --- a/src/audio/host-zephyr.c +++ b/src/audio/host-zephyr.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -748,6 +749,7 @@ __cold static struct comp_dev *host_new(const struct comp_driver *drv, struct comp_dev *dev; struct host_data *hd; const struct ipc_config_host *ipc_host = spec; + struct k_heap *heap = NULL; int ret; assert_can_be_cold(); @@ -759,10 +761,17 @@ __cold static struct comp_dev *host_new(const struct comp_driver *drv, return NULL; dev->ipc_config = *config; - hd = rzalloc(SOF_MEM_FLAG_USER, sizeof(*hd)); +#ifdef CONFIG_SOF_USERSPACE_LL + heap = zephyr_ll_user_heap(); +#endif + + hd = sof_heap_alloc(heap, SOF_MEM_FLAG_USER, sizeof(*hd), 0); if (!hd) goto e_data; + memset(hd, 0, sizeof(*hd)); + hd->heap = heap; + hd->nobytes_last_logged = k_uptime_get(); comp_set_drvdata(dev, hd); @@ -775,7 +784,7 @@ __cold static struct comp_dev *host_new(const struct comp_driver *drv, return dev; e_dev: - rfree(hd); + sof_heap_free(heap, hd); e_data: comp_free_device(dev); return NULL; @@ -807,7 +816,7 @@ __cold static void host_free(struct comp_dev *dev) comp_dbg(dev, "entry"); host_common_free(hd); - rfree(hd); + sof_heap_free(hd->heap, hd); comp_free_device(dev); } @@ -965,8 +974,7 @@ int host_common_params(struct host_data *hd, struct comp_dev *dev, } } else { /* allocate not shared buffer */ - hd->dma_buffer = buffer_alloc_range(sof_sys_user_heap_get(), - buffer_size_preferred, buffer_size, + hd->dma_buffer = buffer_alloc_range(hd->heap, buffer_size_preferred, buffer_size, SOF_MEM_FLAG_USER | SOF_MEM_FLAG_DMA, addr_align, BUFFER_USAGE_NOT_SHARED); if (!hd->dma_buffer) { @@ -1023,8 +1031,8 @@ int host_common_params(struct host_data *hd, struct comp_dev *dev, memset(dma_cfg, 0, sizeof(*dma_cfg)); - dma_block_cfg = rzalloc(SOF_MEM_FLAG_USER, - sizeof(*dma_block_cfg)); + dma_block_cfg = sof_heap_alloc(hd->heap, SOF_MEM_FLAG_USER, + sizeof(*dma_block_cfg), 0); if (!dma_block_cfg) { comp_err(dev, "dma_block_config allocation failed"); @@ -1032,6 +1040,8 @@ int host_common_params(struct host_data *hd, struct comp_dev *dev, goto err_release_channel; } + memset(dma_block_cfg, 0, sizeof(*dma_block_cfg)); + dma_cfg->block_count = 1; dma_cfg->source_data_size = config->src_width; dma_cfg->dest_data_size = config->dest_width; @@ -1113,7 +1123,7 @@ int host_common_params(struct host_data *hd, struct comp_dev *dev, err_free_block_cfg: dma_cfg->head_block = NULL; - rfree(dma_block_cfg); + sof_heap_free(hd->heap, dma_block_cfg); err_release_channel: sof_dma_release_channel(hd->dma, hd->chan_index); hd->chan_index = -1; @@ -1193,7 +1203,7 @@ void host_common_reset(struct host_data *hd, uint16_t state) /* free DMA block configuration */ if (hd->z_config.head_block) - rfree(hd->z_config.head_block); + sof_heap_free(hd->heap, hd->z_config.head_block); /* reset buffer pointers */ hd->local_pos = 0; From 114f88ab860d21fc73c8729b6821afa0c241409e Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 19 Feb 2026 14:51:14 +0200 Subject: [PATCH 028/106] (---section host-zephyr STOP) From e68bd5816ed76dfe3541b30d8339c0afaff55e04 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 19 Feb 2026 14:51:22 +0200 Subject: [PATCH 029/106] (---section dai-zephyr START) From ad044b8851008f30a0acd727aa50edd1f155f418 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Tue, 17 Feb 2026 18:36:07 +0200 Subject: [PATCH 030/106] audio: pcm_converter: make global tables available to user-space The pcm_converter depends on a set of global function tables to set up correct conversion functions. These objects need to be made available to user-space threads, so that pcm_converter can be also run in user-space. No impact to kernel-space use of pcm_converter. Signed-off-by: Kai Vehmanen --- src/audio/pcm_converter/pcm_converter_generic.c | 9 +++++---- src/audio/pcm_converter/pcm_converter_hifi3.c | 9 +++++---- src/audio/pcm_converter/pcm_remap.c | 5 +++-- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/audio/pcm_converter/pcm_converter_generic.c b/src/audio/pcm_converter/pcm_converter_generic.c index fe96e6d1f124..4938be438dbc 100644 --- a/src/audio/pcm_converter/pcm_converter_generic.c +++ b/src/audio/pcm_converter/pcm_converter_generic.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -669,7 +670,7 @@ static int pcm_convert_f_to_s32(const struct audio_stream *source, } #endif /* CONFIG_PCM_CONVERTER_FORMAT_FLOAT && CONFIG_PCM_CONVERTER_FORMAT_S32LE */ -const struct pcm_func_map pcm_func_map[] = { +APP_TASK_DATA const struct pcm_func_map pcm_func_map[] = { #if CONFIG_PCM_CONVERTER_FORMAT_U8 { SOF_IPC_FRAME_U8, SOF_IPC_FRAME_U8, just_copy }, #endif /* CONFIG_PCM_CONVERTER_FORMAT_U8 */ @@ -732,7 +733,7 @@ const struct pcm_func_map pcm_func_map[] = { #endif /* CONFIG_PCM_CONVERTER_FORMAT_FLOAT && CONFIG_PCM_CONVERTER_FORMAT_S32LE */ }; -const size_t pcm_func_count = ARRAY_SIZE(pcm_func_map); +APP_TASK_DATA const size_t pcm_func_count = ARRAY_SIZE(pcm_func_map); #if CONFIG_PCM_CONVERTER_FORMAT_S16_C16_AND_S16_C32 static int pcm_convert_s16_c16_to_s16_c32(const struct audio_stream *source, @@ -1020,7 +1021,7 @@ static int pcm_convert_s24_c32_to_s24_c24_link_gtw(const struct audio_stream *so #endif -const struct pcm_func_vc_map pcm_func_vc_map[] = { +APP_TASK_DATA const struct pcm_func_vc_map pcm_func_vc_map[] = { #if CONFIG_PCM_CONVERTER_FORMAT_S16_C16_AND_S16_C32 { SOF_IPC_FRAME_S16_LE, SOF_IPC_FRAME_S16_LE, SOF_IPC_FRAME_S32_LE, SOF_IPC_FRAME_S16_LE, pcm_convert_s16_c16_to_s16_c32 }, @@ -1101,6 +1102,6 @@ const struct pcm_func_vc_map pcm_func_vc_map[] = { #endif }; -const size_t pcm_func_vc_count = ARRAY_SIZE(pcm_func_vc_map); +APP_TASK_DATA const size_t pcm_func_vc_count = ARRAY_SIZE(pcm_func_vc_map); #endif diff --git a/src/audio/pcm_converter/pcm_converter_hifi3.c b/src/audio/pcm_converter/pcm_converter_hifi3.c index 2b6ca607415d..7c75e326b1c0 100644 --- a/src/audio/pcm_converter/pcm_converter_hifi3.c +++ b/src/audio/pcm_converter/pcm_converter_hifi3.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -764,7 +765,7 @@ static int pcm_convert_f_to_s32(const struct audio_stream *source, #endif /* CONFIG_PCM_CONVERTER_FORMAT_FLOAT && CONFIG_PCM_CONVERTER_FORMAT_32LE */ #endif /* XCHAL_HAVE_FP */ -const struct pcm_func_map pcm_func_map[] = { +APP_TASK_DATA const struct pcm_func_map pcm_func_map[] = { #if CONFIG_PCM_CONVERTER_FORMAT_S16LE { SOF_IPC_FRAME_S16_LE, SOF_IPC_FRAME_S16_LE, just_copy }, #endif /* CONFIG_PCM_CONVERTER_FORMAT_S16LE */ @@ -807,7 +808,7 @@ const struct pcm_func_map pcm_func_map[] = { #endif /* CONFIG_PCM_CONVERTER_FORMAT_FLOAT && CONFIG_PCM_CONVERTER_FORMAT_S32LE */ #endif /* XCHAL_HAVE_FP */ }; -const size_t pcm_func_count = ARRAY_SIZE(pcm_func_map); +APP_TASK_DATA const size_t pcm_func_count = ARRAY_SIZE(pcm_func_map); #if CONFIG_PCM_CONVERTER_FORMAT_S16_C16_AND_S16_C32 static int pcm_convert_s16_c16_to_s16_c32(const struct audio_stream *source, @@ -1206,7 +1207,7 @@ static int pcm_convert_s24_c32_to_s24_c24(const struct audio_stream *source, */ #endif -const struct pcm_func_vc_map pcm_func_vc_map[] = { +APP_TASK_DATA const struct pcm_func_vc_map pcm_func_vc_map[] = { #if CONFIG_PCM_CONVERTER_FORMAT_S16_C16_AND_S16_C32 { SOF_IPC_FRAME_S16_LE, SOF_IPC_FRAME_S16_LE, SOF_IPC_FRAME_S32_LE, SOF_IPC_FRAME_S16_LE, pcm_convert_s16_c16_to_s16_c32 }, @@ -1283,6 +1284,6 @@ const struct pcm_func_vc_map pcm_func_vc_map[] = { #endif }; -const size_t pcm_func_vc_count = ARRAY_SIZE(pcm_func_vc_map); +APP_TASK_DATA const size_t pcm_func_vc_count = ARRAY_SIZE(pcm_func_vc_map); #endif diff --git a/src/audio/pcm_converter/pcm_remap.c b/src/audio/pcm_converter/pcm_remap.c index 9204b21ee8ab..4ae300e195c9 100644 --- a/src/audio/pcm_converter/pcm_remap.c +++ b/src/audio/pcm_converter/pcm_remap.c @@ -5,6 +5,7 @@ #include #include +#include static void mute_channel_c16(struct audio_stream *stream, int channel, int frames) { @@ -423,7 +424,7 @@ static int remap_c16_to_c32_no_shift(const struct audio_stream *source, uint32_t /* Unfortunately, all these nice "if"s were commented out to suppress * CI "defined but not used" warnings. */ -const struct pcm_func_map pcm_remap_func_map[] = { +APP_TASK_DATA const struct pcm_func_map pcm_remap_func_map[] = { /* #if CONFIG_PCM_CONVERTER_FORMAT_S16LE */ { SOF_IPC_FRAME_S16_LE, SOF_IPC_FRAME_S16_LE, remap_c16}, /* #endif */ @@ -474,4 +475,4 @@ const struct pcm_func_map pcm_remap_func_map[] = { /* #endif */ }; -const size_t pcm_remap_func_count = ARRAY_SIZE(pcm_remap_func_map); +APP_TASK_DATA const size_t pcm_remap_func_count = ARRAY_SIZE(pcm_remap_func_map); From 418948073e13814a151be51b1098fe62765dc283 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Tue, 17 Feb 2026 18:39:00 +0200 Subject: [PATCH 031/106] audio: buffer: replace notifier events with direct probe callbacks Replace the generic notifier dispatch for buffer produce, consume, and free events with direct function pointer callbacks on struct comp_buffer. The only consumer was probe.c. - Add probe_cb_produce, probe_cb_free, probe_cb_arg to comp_buffer under CONFIG_PROBE guard - Call probe callbacks directly from comp_buffer_free() and comp_update_buffer_produce() - Remove dead BUFFER_CONSUME notifier event (no listeners) - Remove NOTIFIER_ID_BUFFER_PRODUCE/CONSUME/FREE from notifier.h - Remove notifier.h include from comp_buffer.c and probe.c This change allows to run more audio modules in user-space, where the notifier framework is not available. Signed-off-by: Kai Vehmanen --- src/audio/buffers/comp_buffer.c | 44 +++++++++++------------------ src/include/sof/audio/buffer.h | 9 ++++++ src/include/sof/lib/notifier.h | 3 -- src/probe/probe.c | 49 ++++++++++++++------------------- 4 files changed, 46 insertions(+), 59 deletions(-) diff --git a/src/audio/buffers/comp_buffer.c b/src/audio/buffers/comp_buffer.c index 6804bbf71f04..26c3713e30cb 100644 --- a/src/audio/buffers/comp_buffer.c +++ b/src/audio/buffers/comp_buffer.c @@ -16,7 +16,6 @@ #include #include #include -#include #include #include #include @@ -147,17 +146,12 @@ static void comp_buffer_free(struct sof_audio_buffer *audio_buffer) struct comp_buffer *buffer = container_of(audio_buffer, struct comp_buffer, audio_buffer); - struct buffer_cb_free cb_data = { - .buffer = buffer, - }; - buf_dbg(buffer, "buffer_free()"); - notifier_event(buffer, NOTIFIER_ID_BUFFER_FREE, - NOTIFIER_TARGET_CORE_LOCAL, &cb_data, sizeof(cb_data)); - - /* In case some listeners didn't unregister from buffer's callbacks */ - notifier_unregister_all(NULL, buffer); +#if CONFIG_PROBE + if (buffer->probe_cb_free) + buffer->probe_cb_free(buffer->probe_cb_arg); +#endif struct k_heap *heap = buffer->audio_buffer.heap; @@ -470,12 +464,6 @@ bool buffer_params_match(struct comp_buffer *buffer, void comp_update_buffer_produce(struct comp_buffer *buffer, uint32_t bytes) { - struct buffer_cb_transact cb_data = { - .buffer = buffer, - .transaction_amount = bytes, - .transaction_begin_address = audio_stream_get_wptr(&buffer->stream), - }; - /* return if no bytes */ if (!bytes) { #if CONFIG_SOF_LOG_DBG_BUFFER @@ -491,10 +479,19 @@ void comp_update_buffer_produce(struct comp_buffer *buffer, uint32_t bytes) return; } - audio_stream_produce(&buffer->stream, bytes); +#if CONFIG_PROBE + if (buffer->probe_cb_produce) { + struct buffer_cb_transact cb_data = { + .buffer = buffer, + .transaction_amount = bytes, + .transaction_begin_address = audio_stream_get_wptr(&buffer->stream), + }; - notifier_event(buffer, NOTIFIER_ID_BUFFER_PRODUCE, - NOTIFIER_TARGET_CORE_LOCAL, &cb_data, sizeof(cb_data)); + buffer->probe_cb_produce(buffer->probe_cb_arg, &cb_data); + } +#endif + + audio_stream_produce(&buffer->stream, bytes); #if CONFIG_SOF_LOG_DBG_BUFFER buf_dbg(buffer, "((buffer->avail << 16) | buffer->free) = %08x, ((buffer->id << 16) | buffer->size) = %08x", @@ -511,12 +508,6 @@ void comp_update_buffer_produce(struct comp_buffer *buffer, uint32_t bytes) void comp_update_buffer_consume(struct comp_buffer *buffer, uint32_t bytes) { - struct buffer_cb_transact cb_data = { - .buffer = buffer, - .transaction_amount = bytes, - .transaction_begin_address = audio_stream_get_rptr(&buffer->stream), - }; - CORE_CHECK_STRUCT(&buffer->audio_buffer); /* return if no bytes */ @@ -536,9 +527,6 @@ void comp_update_buffer_consume(struct comp_buffer *buffer, uint32_t bytes) audio_stream_consume(&buffer->stream, bytes); - notifier_event(buffer, NOTIFIER_ID_BUFFER_CONSUME, - NOTIFIER_TARGET_CORE_LOCAL, &cb_data, sizeof(cb_data)); - #if CONFIG_SOF_LOG_DBG_BUFFER buf_dbg(buffer, "(buffer->avail << 16) | buffer->free = %08x, (buffer->id << 16) | buffer->size = %08x, (buffer->r_ptr - buffer->addr) << 16 | (buffer->w_ptr - buffer->addr)) = %08x", (audio_stream_get_avail_bytes(&buffer->stream) << 16) | diff --git a/src/include/sof/audio/buffer.h b/src/include/sof/audio/buffer.h index 5d5e64e8043d..39926331ea78 100644 --- a/src/include/sof/audio/buffer.h +++ b/src/include/sof/audio/buffer.h @@ -151,6 +151,15 @@ struct comp_buffer { struct list_item buffers_list; struct k_heap *heap; + +#if CONFIG_PROBE + /** probe produce callback, called on buffer produce */ + void (*probe_cb_produce)(void *arg, struct buffer_cb_transact *cb_data); + /** probe free callback, called on buffer free */ + void (*probe_cb_free)(void *arg); + /** opaque argument passed to probe callbacks */ + void *probe_cb_arg; +#endif }; /* diff --git a/src/include/sof/lib/notifier.h b/src/include/sof/lib/notifier.h index 060906655cb8..87ca2cd40265 100644 --- a/src/include/sof/lib/notifier.h +++ b/src/include/sof/lib/notifier.h @@ -27,9 +27,6 @@ enum notify_id { NOTIFIER_ID_SSP_FREQ, /* struct clock_notify_data * */ NOTIFIER_ID_KPB_CLIENT_EVT, /* struct kpb_event_data * */ NOTIFIER_ID_DMA_DOMAIN_CHANGE, /* struct dma_chan_data * */ - NOTIFIER_ID_BUFFER_PRODUCE, /* struct buffer_cb_transact* */ - NOTIFIER_ID_BUFFER_CONSUME, /* struct buffer_cb_transact* */ - NOTIFIER_ID_BUFFER_FREE, /* struct buffer_cb_free* */ NOTIFIER_ID_DMA_COPY, /* struct dma_cb_data* */ NOTIFIER_ID_LL_POST_RUN, /* NULL */ NOTIFIER_ID_DMA_IRQ, /* struct dma_chan_data * */ diff --git a/src/probe/probe.c b/src/probe/probe.c index 088a2eb1773b..19f62bf81a14 100644 --- a/src/probe/probe.c +++ b/src/probe/probe.c @@ -13,7 +13,6 @@ #include #include #include -#include #include #include #include @@ -902,14 +901,12 @@ static ssize_t probe_logging_hook(uint8_t *buffer, size_t length) * Extraction probe: generate format, header and copy data to probe buffer. * Injection probe: find corresponding DMA, check avail data, copy data, * update pointers and request more data from host if needed. - * \param[in] arg pointer (not used). - * \param[in] type of notify. - * \param[in] data pointer. + * \param[in] arg pointer to buffer_id. + * \param[in] cb_data pointer to buffer callback transaction data. */ -static void probe_cb_produce(void *arg, enum notify_id type, void *data) +static void probe_cb_produce(void *arg, struct buffer_cb_transact *cb_data) { struct probe_pdata *_probe = probe_get(); - struct buffer_cb_transact *cb_data = data; struct comp_buffer *buffer = cb_data->buffer; struct probe_dma_ext *dma; uint32_t buffer_id; @@ -921,7 +918,7 @@ static void probe_cb_produce(void *arg, enum notify_id type, void *data) uint32_t format; uint64_t checksum; - buffer_id = *(int *)arg; + buffer_id = *(uint32_t *)arg; /* search for probe point connected to this buffer */ for (i = 0; i < CONFIG_PROBE_POINTS_MAX; i++) @@ -1068,13 +1065,11 @@ static void probe_cb_produce(void *arg, enum notify_id type, void *data) /** * \brief Callback for buffer free, it will remove probe point. - * \param[in] arg pointer (not used). - * \param[in] type of notify. - * \param[in] data pointer. + * \param[in] arg pointer to buffer_id. */ -static void probe_cb_free(void *arg, enum notify_id type, void *data) +static void probe_cb_free(void *arg) { - uint32_t buffer_id = *(int *)arg; + uint32_t buffer_id = *(uint32_t *)arg; int ret; tr_dbg(&pr_tr, "buffer_id = %u", buffer_id); @@ -1315,16 +1310,13 @@ int probe_point_add(uint32_t count, const struct probe_point *probe) probe_point_id_t *new_buf_id = &_probe->probe_points[first_free].buffer_id; #if CONFIG_IPC_MAJOR_4 - notifier_register(&new_buf_id->full_id, buf, NOTIFIER_ID_BUFFER_PRODUCE, - &probe_cb_produce, 0); - notifier_register(&new_buf_id->full_id, buf, NOTIFIER_ID_BUFFER_FREE, - &probe_cb_free, 0); + struct comp_buffer *probe_buf = buf; #else - notifier_register(&new_buf_id->full_id, dev->cb, NOTIFIER_ID_BUFFER_PRODUCE, - &probe_cb_produce, 0); - notifier_register(&new_buf_id->full_id, dev->cb, NOTIFIER_ID_BUFFER_FREE, - &probe_cb_free, 0); + struct comp_buffer *probe_buf = (struct comp_buffer *)dev->cb; #endif + probe_buf->probe_cb_produce = probe_cb_produce; + probe_buf->probe_cb_free = probe_cb_free; + probe_buf->probe_cb_arg = &new_buf_id->full_id; } } @@ -1444,19 +1436,20 @@ int probe_point_remove(uint32_t count, const uint32_t *buffer_id) if (dev) { buf = ipc4_get_buffer(dev, *buf_id); if (buf) { - notifier_unregister(NULL, buf, - NOTIFIER_ID_BUFFER_PRODUCE); - notifier_unregister(NULL, buf, - NOTIFIER_ID_BUFFER_FREE); + buf->probe_cb_produce = NULL; + buf->probe_cb_free = NULL; + buf->probe_cb_arg = NULL; } } #else dev = ipc_get_comp_by_id(ipc_get(), buffer_id[i]); if (dev) { - notifier_unregister(&buf_id->full_id, dev->cb, - NOTIFIER_ID_BUFFER_PRODUCE); - notifier_unregister(&buf_id->full_id, dev->cb, - NOTIFIER_ID_BUFFER_FREE); + struct comp_buffer *probe_buf = + (struct comp_buffer *)dev->cb; + + probe_buf->probe_cb_produce = NULL; + probe_buf->probe_cb_free = NULL; + probe_buf->probe_cb_arg = NULL; } #endif _probe->probe_points[j].stream_tag = From d67e32b87e299155fe6d72765dd859cf62f2ffc2 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Tue, 17 Feb 2026 13:22:08 +0200 Subject: [PATCH 032/106] audio: dai-zephyr: make memory allocations user-space compatible Convert all memory allocations to use the sof_heap_alloc() interface and pass the dai_data specific heap object. This makes dai-zephyr code compatible with use from user-space, but does not effect kernel space use. Signed-off-by: Kai Vehmanen --- src/audio/dai-zephyr.c | 52 ++++++++++++++++++++++---------- src/include/sof/lib/dai-zephyr.h | 1 + src/ipc/ipc4/dai.c | 6 ++-- 3 files changed, 41 insertions(+), 18 deletions(-) diff --git a/src/audio/dai-zephyr.c b/src/audio/dai-zephyr.c index 04a1a319301e..ea937e0533af 100644 --- a/src/audio/dai-zephyr.c +++ b/src/audio/dai-zephyr.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -525,6 +526,15 @@ __cold int dai_common_new(struct dai_data *dd, struct comp_dev *dev, dd->xrun = 0; dd->chan = NULL; +#ifdef CONFIG_SOF_USERSPACE_LL + /* + * copier_dai_create() uses mod_zalloc() to allocate + * the 'dd' dai data object and does not set dd->heap. + * If LL is run in user-space, assign the 'heap' here. + */ + dd->heap = zephyr_ll_user_heap(); +#endif + /* I/O performance init, keep it last so the function does not reach this in case * of return on error, so that we do not waste a slot */ @@ -577,6 +587,7 @@ __cold static struct comp_dev *dai_new(const struct comp_driver *drv, struct comp_dev *dev; const struct ipc_config_dai *dai_cfg = spec; struct dai_data *dd; + struct k_heap *heap = NULL; int ret; assert_can_be_cold(); @@ -589,10 +600,17 @@ __cold static struct comp_dev *dai_new(const struct comp_driver *drv, dev->ipc_config = *config; - dd = rzalloc(SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, sizeof(*dd)); +#ifdef CONFIG_SOF_USERSPACE_LL + heap = zephyr_ll_user_heap(); +#endif + + dd = sof_heap_alloc(heap, SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, sizeof(*dd), 0); if (!dd) goto e_data; + memset(dd, 0, sizeof(*dd)); + dd->heap = heap; + comp_set_drvdata(dev, dd); ret = dai_common_new(dd, dev, dai_cfg); @@ -606,7 +624,7 @@ __cold static struct comp_dev *dai_new(const struct comp_driver *drv, return dev; error: - rfree(dd); + sof_heap_free(dd->heap, dd); e_data: comp_free_device(dev); return NULL; @@ -634,7 +652,7 @@ __cold void dai_common_free(struct dai_data *dd) dai_put(dd->dai); - rfree(dd->dai_spec_config); + sof_heap_free(dd->heap, dd->dai_spec_config); } __cold static void dai_free(struct comp_dev *dev) @@ -648,7 +666,7 @@ __cold static void dai_free(struct comp_dev *dev) dai_common_free(dd); - rfree(dd); + sof_heap_free(dd->heap, dd); comp_free_device(dev); } @@ -843,7 +861,7 @@ static int dai_set_sg_config(struct dai_data *dd, struct comp_dev *dev, uint32_t } while (--max_block_count > 0); } - err = dma_sg_alloc(NULL, &config->elem_array, SOF_MEM_FLAG_USER, + err = dma_sg_alloc(dd->heap, &config->elem_array, SOF_MEM_FLAG_USER, config->direction, period_count, period_bytes, @@ -869,8 +887,9 @@ static int dai_set_dma_config(struct dai_data *dd, struct comp_dev *dev) comp_dbg(dev, "entry"); - dma_cfg = rballoc(SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT | SOF_MEM_FLAG_DMA, - sizeof(struct dma_config)); + dma_cfg = sof_heap_alloc(dd->heap, + SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT | SOF_MEM_FLAG_DMA, + sizeof(struct dma_config), 0); if (!dma_cfg) { comp_err(dev, "dma_cfg allocation failed"); return -ENOMEM; @@ -899,10 +918,11 @@ static int dai_set_dma_config(struct dai_data *dd, struct comp_dev *dev) else dma_cfg->dma_slot = config->src_dev; - dma_block_cfg = rballoc(SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT | SOF_MEM_FLAG_DMA, - sizeof(struct dma_block_config) * dma_cfg->block_count); + dma_block_cfg = sof_heap_alloc(dd->heap, + SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT | SOF_MEM_FLAG_DMA, + sizeof(struct dma_block_config) * dma_cfg->block_count, 0); if (!dma_block_cfg) { - rfree(dma_cfg); + sof_heap_free(dd->heap, dma_cfg); comp_err(dev, "dma_block_config allocation failed"); return -ENOMEM; } @@ -1036,7 +1056,7 @@ static int dai_set_dma_buffer(struct dai_data *dd, struct comp_dev *dev, return err; } } else { - dd->dma_buffer = buffer_alloc_range(NULL, buffer_size_preferred, buffer_size, + dd->dma_buffer = buffer_alloc_range(dd->heap, buffer_size_preferred, buffer_size, SOF_MEM_FLAG_USER | SOF_MEM_FLAG_DMA, addr_align, BUFFER_USAGE_NOT_SHARED); if (!dd->dma_buffer) { @@ -1124,8 +1144,8 @@ int dai_common_params(struct dai_data *dd, struct comp_dev *dev, if (err < 0) { buffer_free(dd->dma_buffer); dd->dma_buffer = NULL; - dma_sg_free(NULL, &config->elem_array); - rfree(dd->z_config); + dma_sg_free(dd->heap, &config->elem_array); + sof_heap_free(dd->heap, dd->z_config); dd->z_config = NULL; } @@ -1255,10 +1275,10 @@ void dai_common_reset(struct dai_data *dd, struct comp_dev *dev) if (!dd->delayed_dma_stop) dai_dma_release(dd, dev); - dma_sg_free(NULL, &config->elem_array); + dma_sg_free(dd->heap, &config->elem_array); if (dd->z_config) { - rfree(dd->z_config->head_block); - rfree(dd->z_config); + sof_heap_free(dd->heap, dd->z_config->head_block); + sof_heap_free(dd->heap, dd->z_config); dd->z_config = NULL; } diff --git a/src/include/sof/lib/dai-zephyr.h b/src/include/sof/lib/dai-zephyr.h index cb996e147c4f..a6e0bd8c97fe 100644 --- a/src/include/sof/lib/dai-zephyr.h +++ b/src/include/sof/lib/dai-zephyr.h @@ -168,6 +168,7 @@ struct dai_data { #endif /* Copier gain params */ struct copier_gain_params *gain_data; + struct k_heap *heap; }; /* these 3 are here to satisfy clk.c and ssp.h interconnection, will be removed leter */ diff --git a/src/ipc/ipc4/dai.c b/src/ipc/ipc4/dai.c index 501b60ef8a5c..7fbeddfb7ba5 100644 --- a/src/ipc/ipc4/dai.c +++ b/src/ipc/ipc4/dai.c @@ -400,15 +400,17 @@ __cold int dai_config(struct dai_data *dd, struct comp_dev *dev, /* allocated dai_config if not yet */ if (!dd->dai_spec_config) { size = sizeof(*copier_cfg); - dd->dai_spec_config = rzalloc(SOF_MEM_FLAG_USER, size); + dd->dai_spec_config = sof_heap_alloc(dd->heap, SOF_MEM_FLAG_USER, size, 0); if (!dd->dai_spec_config) { comp_err(dev, "No memory for size %d", size); return -ENOMEM; } + memset(dd->dai_spec_config, 0, size); + ret = memcpy_s(dd->dai_spec_config, size, copier_cfg, size); if (ret < 0) { - rfree(dd->dai_spec_config); + sof_heap_free(dd->heap, dd->dai_spec_config); dd->dai_spec_config = NULL; return -EINVAL; } From 720a9617723bc2bbddd3a20db44a1caaff71d5e0 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Tue, 17 Feb 2026 13:45:44 +0200 Subject: [PATCH 033/106] lib: dai: make dai_get() and dai_put() compatible with user-space The dai_get()/dai_put() provide a helper to access DAI devices. When used in user-space, the wrapper struct should be created in user-space memory. Signed-off-by: Kai Vehmanen --- src/lib/dai.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/lib/dai.c b/src/lib/dai.c index 58ff68a2e57e..0af3d99ceb51 100644 --- a/src/lib/dai.c +++ b/src/lib/dai.c @@ -11,6 +11,7 @@ #include #include #include +#include /* for zephyr_ll_user_heap() */ #include #include #include @@ -304,6 +305,11 @@ struct dai *dai_get(uint32_t type, uint32_t index, uint32_t flags) { const struct device *dev; struct dai *d; + struct k_heap *heap = NULL; + +#ifdef CONFIG_SOF_USERSPACE_LL + heap = zephyr_ll_user_heap(); +#endif dev = dai_get_device(type, index); if (!dev) { @@ -312,10 +318,12 @@ struct dai *dai_get(uint32_t type, uint32_t index, uint32_t flags) return NULL; } - d = rzalloc(SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, sizeof(struct dai)); + d = sof_heap_alloc(heap, SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, sizeof(struct dai), 0); if (!d) return NULL; + memset(d, 0, sizeof(struct dai)); + d->index = index; d->type = type; d->dev = dev; @@ -325,7 +333,7 @@ struct dai *dai_get(uint32_t type, uint32_t index, uint32_t flags) if (dai_probe(d->dev)) { tr_err(&dai_tr, "dai_get: failed to probe dai with index %d type %d", index, type); - rfree(d); + sof_heap_free(heap, d); return NULL; } @@ -336,6 +344,11 @@ struct dai *dai_get(uint32_t type, uint32_t index, uint32_t flags) void dai_put(struct dai *dai) { int ret; + struct k_heap *heap = NULL; + +#ifdef CONFIG_SOF_USERSPACE_LL + heap = zephyr_ll_user_heap(); +#endif ret = dai_remove(dai->dev); if (ret < 0) { @@ -343,7 +356,7 @@ void dai_put(struct dai *dai) dai->index, ret); } - rfree(dai); + sof_heap_free(heap, dai); } #else static inline const struct dai_type_info *dai_find_type(uint32_t type) From 00b7b6b349d2a5b2c8e38d2278192199025e9f70 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Tue, 17 Feb 2026 18:34:48 +0200 Subject: [PATCH 034/106] audio: dai-zephyr: rework calls to DMA driver, remove channel pointer For historical reasons, dai-zephyr has somewhat complicated code to manage the DMA channel instance information. When a DMA channel is allocated, a pointer to the system DMA channel table is acquired and some additional information is stored per channel. This is however redundant as the only piece of information actually needed is the channel index. Simplify the code by not storing the channel pointer anymore, but rather just store the channel index and use that in all calls to the DMA driver. Signed-off-by: Kai Vehmanen --- src/audio/copier/copier_dai.c | 2 + src/audio/dai-zephyr.c | 63 +++++++++++++++----------------- src/include/sof/lib/dai-zephyr.h | 2 +- src/ipc/ipc4/dai.c | 19 +++++----- 4 files changed, 42 insertions(+), 44 deletions(-) diff --git a/src/audio/copier/copier_dai.c b/src/audio/copier/copier_dai.c index dfd2590c7108..f458fa14405d 100644 --- a/src/audio/copier/copier_dai.c +++ b/src/audio/copier/copier_dai.c @@ -208,6 +208,8 @@ __cold static int copier_dai_init(struct comp_dev *dev, if (!dd) return -ENOMEM; memset(dd, 0, sizeof(*dd)); + dd->chan_index = -1; + comp_info(dev, "dd %p initialized, index %d", dd, dd->chan_index); ret = dai_common_new(dd, dev, dai); if (ret < 0) diff --git a/src/audio/dai-zephyr.c b/src/audio/dai-zephyr.c index ea937e0533af..d1b713fbe4a0 100644 --- a/src/audio/dai-zephyr.c +++ b/src/audio/dai-zephyr.c @@ -524,7 +524,6 @@ __cold int dai_common_new(struct dai_data *dd, struct comp_dev *dev, dma_sg_init(&dd->config.elem_array); dd->xrun = 0; - dd->chan = NULL; #ifdef CONFIG_SOF_USERSPACE_LL /* @@ -610,6 +609,8 @@ __cold static struct comp_dev *dai_new(const struct comp_driver *drv, memset(dd, 0, sizeof(*dd)); dd->heap = heap; + dd->chan_index = -1; + comp_info(dev, "dd %p initialized, index %d", dd, dd->chan_index); comp_set_drvdata(dev, dd); @@ -641,10 +642,8 @@ __cold void dai_common_free(struct dai_data *dd) if (dd->group) dai_group_put(dd->group); - if (dd->chan) { - sof_dma_release_channel(dd->dma, dd->chan->index); - dd->chan->dev_data = NULL; - } + if (dd->chan_index != -1) + sof_dma_release_channel(dd->dma, dd->chan_index); sof_dma_put(dd->dma); @@ -1176,9 +1175,9 @@ int dai_common_config_prepare(struct dai_data *dd, struct comp_dev *dev) return -EINVAL; } - if (dd->chan) { + if (dd->chan_index != -1) { comp_info(dev, "dma channel index %d already configured", - dd->chan->index); + dd->chan_index); return 0; } @@ -1192,18 +1191,14 @@ int dai_common_config_prepare(struct dai_data *dd, struct comp_dev *dev) } /* get DMA channel */ - channel = sof_dma_request_channel(dd->dma, channel); - if (channel < 0) { + dd->chan_index = sof_dma_request_channel(dd->dma, channel); + if (dd->chan_index < 0) { comp_err(dev, "dma_request_channel() failed"); - dd->chan = NULL; return -EIO; } - dd->chan = &dd->dma->chan[channel]; - dd->chan->dev_data = dd; - comp_dbg(dev, "new configured dma channel index %d", - dd->chan->index); + dd->chan_index); return 0; } @@ -1214,8 +1209,8 @@ int dai_common_prepare(struct dai_data *dd, struct comp_dev *dev) dd->total_data_processed = 0; - if (!dd->chan) { - comp_err(dev, "Missing dd->chan."); + if (dd->chan_index == -1) { + comp_err(dev, "Missing dd->chan_index."); comp_set_state(dev, COMP_TRIGGER_RESET); return -EINVAL; } @@ -1236,7 +1231,7 @@ int dai_common_prepare(struct dai_data *dd, struct comp_dev *dev) return 0; } - ret = sof_dma_config(dd->chan->dma, dd->chan->index, dd->z_config); + ret = sof_dma_config(dd->dma, dd->chan_index, dd->z_config); if (ret < 0) comp_set_state(dev, COMP_TRIGGER_RESET); @@ -1323,7 +1318,7 @@ static int dai_comp_trigger_internal(struct dai_data *dd, struct comp_dev *dev, /* only start the DAI if we are not XRUN handling */ if (dd->xrun == 0) { - ret = sof_dma_start(dd->chan->dma, dd->chan->index); + ret = sof_dma_start(dd->dma, dd->chan_index); if (ret < 0) return ret; @@ -1361,16 +1356,16 @@ static int dai_comp_trigger_internal(struct dai_data *dd, struct comp_dev *dev, /* only start the DAI if we are not XRUN handling */ if (dd->xrun == 0) { /* recover valid start position */ - ret = sof_dma_stop(dd->chan->dma, dd->chan->index); + ret = sof_dma_stop(dd->dma, dd->chan_index); if (ret < 0) return ret; /* dma_config needed after stop */ - ret = sof_dma_config(dd->chan->dma, dd->chan->index, dd->z_config); + ret = sof_dma_config(dd->dma, dd->chan_index, dd->z_config); if (ret < 0) return ret; - ret = sof_dma_start(dd->chan->dma, dd->chan->index); + ret = sof_dma_start(dd->dma, dd->chan_index); if (ret < 0) return ret; @@ -1398,11 +1393,11 @@ static int dai_comp_trigger_internal(struct dai_data *dd, struct comp_dev *dev, * as soon as possible. */ #if CONFIG_COMP_DAI_STOP_TRIGGER_ORDER_REVERSE - ret = sof_dma_stop(dd->chan->dma, dd->chan->index); + ret = sof_dma_stop(dd->dma, dd->chan_index); dai_trigger_op(dd->dai, cmd, dev->direction); #else dai_trigger_op(dd->dai, cmd, dev->direction); - ret = sof_dma_stop(dd->chan->dma, dd->chan->index); + ret = sof_dma_stop(dd->dma, dd->chan_index); if (ret) { comp_warn(dev, "dma was stopped earlier"); ret = 0; @@ -1412,11 +1407,11 @@ static int dai_comp_trigger_internal(struct dai_data *dd, struct comp_dev *dev, case COMP_TRIGGER_PAUSE: comp_dbg(dev, "PAUSE"); #if CONFIG_COMP_DAI_STOP_TRIGGER_ORDER_REVERSE - ret = sof_dma_suspend(dd->chan->dma, dd->chan->index); + ret = sof_dma_suspend(dd->dma, dd->chan_index); dai_trigger_op(dd->dai, cmd, dev->direction); #else dai_trigger_op(dd->dai, cmd, dev->direction); - ret = sof_dma_suspend(dd->chan->dma, dd->chan->index); + ret = sof_dma_suspend(dd->dma, dd->chan_index); #endif break; case COMP_TRIGGER_PRE_START: @@ -1514,7 +1509,7 @@ static int dai_comp_trigger(struct comp_dev *dev, int cmd) */ static int dai_get_status(struct comp_dev *dev, struct dai_data *dd, struct dma_status *stat) { - int ret = sof_dma_get_status(dd->chan->dma, dd->chan->index, stat); + int ret = sof_dma_get_status(dd->dma, dd->chan_index, stat); #if CONFIG_XRUN_NOTIFICATIONS_ENABLE if (ret == -EPIPE && !dd->xrun_notification_sent) { dd->xrun_notification_sent = send_copier_gateway_xrun_notif_msg @@ -1619,7 +1614,7 @@ int dai_zephyr_multi_endpoint_copy(struct dai_data **dd, struct comp_dev *dev, #endif for (i = 0; i < num_endpoints; i++) { - ret = sof_dma_reload(dd[i]->chan->dma, dd[i]->chan->index, 0); + ret = sof_dma_reload(dd[i]->dma, dd[i]->chan_index, 0); if (ret < 0) { dai_report_reload_xrun(dd[i], dev, 0); return ret; @@ -1645,10 +1640,10 @@ int dai_zephyr_multi_endpoint_copy(struct dai_data **dd, struct comp_dev *dev, status = dai_dma_multi_endpoint_cb(dd[i], dev, frames, multi_endpoint_buffer); if (status == SOF_DMA_CB_STATUS_END) - sof_dma_stop(dd[i]->chan->dma, dd[i]->chan->index); + sof_dma_stop(dd[i]->dma, dd[i]->chan_index); copy_bytes = frames * audio_stream_frame_bytes(&dd[i]->dma_buffer->stream); - ret = sof_dma_reload(dd[i]->chan->dma, dd[i]->chan->index, copy_bytes); + ret = sof_dma_reload(dd[i]->dma, dd[i]->chan_index, copy_bytes); if (ret < 0) { dai_report_reload_xrun(dd[i], dev, copy_bytes); return ret; @@ -1837,7 +1832,7 @@ int dai_common_copy(struct dai_data *dd, struct comp_dev *dev, pcm_converter_fun comp_warn(dev, "nothing to copy, src_frames: %u, sink_frames: %u", src_frames, sink_frames); #endif - sof_dma_reload(dd->chan->dma, dd->chan->index, 0); + sof_dma_reload(dd->dma, dd->chan_index, 0); return 0; } @@ -1847,9 +1842,9 @@ int dai_common_copy(struct dai_data *dd, struct comp_dev *dev, pcm_converter_fun comp_warn(dev, "dai trigger copy failed"); if (dai_dma_cb(dd, dev, copy_bytes, converter) == SOF_DMA_CB_STATUS_END) - sof_dma_stop(dd->chan->dma, dd->chan->index); + sof_dma_stop(dd->dma, dd->chan_index); - ret = sof_dma_reload(dd->chan->dma, dd->chan->index, copy_bytes); + ret = sof_dma_reload(dd->dma, dd->chan_index, copy_bytes); if (ret < 0) { dai_report_reload_xrun(dd, dev, copy_bytes); return ret; @@ -1887,7 +1882,7 @@ int dai_common_ts_config_op(struct dai_data *dd, struct comp_dev *dev) struct dai_ts_cfg *cfg = &dd->ts_config; comp_dbg(dev, "dai_ts_config()"); - if (!dd->chan) { + if (dd->chan_index == -1) { comp_err(dev, "No DMA channel information"); return -EINVAL; } @@ -1910,7 +1905,7 @@ int dai_common_ts_config_op(struct dai_data *dd, struct comp_dev *dev) cfg->direction = dai->direction; cfg->index = dd->dai->index; cfg->dma_id = dd->dma->plat_data.id; - cfg->dma_chan_index = dd->chan->index; + cfg->dma_chan_index = dd->chan_index; cfg->dma_chan_count = dd->dma->plat_data.channels; return dai_ts_config(dd->dai->dev, cfg); diff --git a/src/include/sof/lib/dai-zephyr.h b/src/include/sof/lib/dai-zephyr.h index a6e0bd8c97fe..716605e157a3 100644 --- a/src/include/sof/lib/dai-zephyr.h +++ b/src/include/sof/lib/dai-zephyr.h @@ -117,7 +117,7 @@ typedef int (*channel_copy_func)(const struct audio_stream *src, unsigned int sr */ struct dai_data { /* local DMA config */ - struct dma_chan_data *chan; + int chan_index; uint32_t stream_id; struct dma_sg_config config; struct dma_config *z_config; diff --git a/src/ipc/ipc4/dai.c b/src/ipc/ipc4/dai.c index 7fbeddfb7ba5..c2b63f1f9a73 100644 --- a/src/ipc/ipc4/dai.c +++ b/src/ipc/ipc4/dai.c @@ -223,7 +223,7 @@ void dai_dma_release(struct dai_data *dd, struct comp_dev *dev) } /* put the allocated DMA channel first */ - if (dd->chan) { + if (dd->chan_index != -1) { struct ipc4_llp_reading_slot slot; if (dd->slot_info.node_id) { @@ -245,15 +245,16 @@ void dai_dma_release(struct dai_data *dd, struct comp_dev *dev) */ #if CONFIG_ZEPHYR_NATIVE_DRIVERS /* if reset is after pause dma has already been stopped */ - dma_stop(dd->chan->dma->z_dev, dd->chan->index); + dma_stop(dd->dma->z_dev, dd->chan_index); - dma_release_channel(dd->chan->dma->z_dev, dd->chan->index); + dma_release_channel(dd->dma->z_dev, dd->chan_index); #else + /* TODO: to remove this, no longer works! */ dma_stop_legacy(dd->chan); dma_channel_put_legacy(dd->chan); -#endif - dd->chan->dev_data = NULL; dd->chan = NULL; +#endif + } } @@ -377,9 +378,9 @@ __cold int dai_config(struct dai_data *dd, struct comp_dev *dev, return 0; } - if (dd->chan) { + if (dd->chan_index != -1) { comp_info(dev, "Configured. dma channel index %d, ignore...", - dd->chan->index); + dd->chan_index); return 0; } @@ -440,7 +441,7 @@ int dai_common_position(struct dai_data *dd, struct comp_dev *dev, platform_dai_wallclock(dev, &dd->wallclock); posn->wallclock = dd->wallclock; - ret = dma_get_status(dd->dma->z_dev, dd->chan->index, &status); + ret = dma_get_status(dd->dma->z_dev, dd->chan_index, &status); if (ret < 0) return ret; @@ -465,7 +466,7 @@ void dai_dma_position_update(struct dai_data *dd, struct comp_dev *dev) if (!dd->slot_info.node_id) return; - ret = dma_get_status(dd->dma->z_dev, dd->chan->index, &status); + ret = dma_get_status(dd->dma->z_dev, dd->chan_index, &status); if (ret < 0) return; From 5a1981f7ff6eb8cb098e33d2865468871e11ede0 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Tue, 17 Feb 2026 21:41:58 +0200 Subject: [PATCH 035/106] audio: dai-zephyr: convert spinlock into mutex for properties The spinlock used to protect access to DAI properties can be converted to a mutex as this is only accessed from IPC and LL threads and both are normal Zephyr threads. As an additional benefit, use of mutex allows to run the dai-zephyr module in user-space. Signed-off-by: Kai Vehmanen --- src/audio/dai-zephyr.c | 53 +++++++++++++++++++------------- src/include/sof/lib/dai-zephyr.h | 5 ++- 2 files changed, 36 insertions(+), 22 deletions(-) diff --git a/src/audio/dai-zephyr.c b/src/audio/dai-zephyr.c index d1b713fbe4a0..990ec3848165 100644 --- a/src/audio/dai-zephyr.c +++ b/src/audio/dai-zephyr.c @@ -222,12 +222,13 @@ __cold int dai_set_config(struct dai *dai, struct ipc_config_dai *common_config, /* called from ipc/ipc3/dai.c */ int dai_get_handshake(struct dai *dai, int direction, int stream_id) { - k_spinlock_key_t key = k_spin_lock(&dai->lock); - const struct dai_properties *props = dai_get_properties(dai->dev, direction, - stream_id); - int hs_id = props->dma_hs_id; + const struct dai_properties *props; + int hs_id; - k_spin_unlock(&dai->lock, key); + k_mutex_lock(dai->lock, K_FOREVER); + props = dai_get_properties(dai->dev, direction, stream_id); + hs_id = props->dma_hs_id; + k_mutex_unlock(dai->lock); return hs_id; } @@ -236,39 +237,41 @@ int dai_get_handshake(struct dai *dai, int direction, int stream_id) int dai_get_fifo_depth(struct dai *dai, int direction) { const struct dai_properties *props; - k_spinlock_key_t key; int fifo_depth; if (!dai) return 0; - key = k_spin_lock(&dai->lock); + k_mutex_lock(dai->lock, K_FOREVER); props = dai_get_properties(dai->dev, direction, 0); fifo_depth = props->fifo_depth; - k_spin_unlock(&dai->lock, key); + k_mutex_unlock(dai->lock); return fifo_depth; } int dai_get_stream_id(struct dai *dai, int direction) { - k_spinlock_key_t key = k_spin_lock(&dai->lock); - const struct dai_properties *props = dai_get_properties(dai->dev, direction, 0); - int stream_id = props->stream_id; + const struct dai_properties *props; + int stream_id; - k_spin_unlock(&dai->lock, key); + k_mutex_lock(dai->lock, K_FOREVER); + props = dai_get_properties(dai->dev, direction, 0); + stream_id = props->stream_id; + k_mutex_unlock(dai->lock); return stream_id; } static int dai_get_fifo(struct dai *dai, int direction, int stream_id) { - k_spinlock_key_t key = k_spin_lock(&dai->lock); - const struct dai_properties *props = dai_get_properties(dai->dev, direction, - stream_id); - int fifo_address = props->fifo_address; + const struct dai_properties *props; + int fifo_address; - k_spin_unlock(&dai->lock, key); + k_mutex_lock(dai->lock, K_FOREVER); + props = dai_get_properties(dai->dev, direction, stream_id); + fifo_address = props->fifo_address; + k_mutex_unlock(dai->lock); return fifo_address; } @@ -520,7 +523,12 @@ __cold int dai_common_new(struct dai_data *dd, struct comp_dev *dev, return -ENODEV; } - k_spinlock_init(&dd->dai->lock); +#ifdef CONFIG_SOF_USERSPACE_LL + dd->dai->lock = k_object_alloc(K_OBJ_MUTEX); +#else + dd->dai->lock = &dd->dai->lock_obj; +#endif + k_mutex_init(dd->dai->lock); dma_sg_init(&dd->config.elem_array); dd->xrun = 0; @@ -649,6 +657,10 @@ __cold void dai_common_free(struct dai_data *dd) dai_release_llp_slot(dd); +#ifdef CONFIG_SOF_USERSPACE_LL + k_object_free(dd->dai->lock); +#endif + dai_put(dd->dai); sof_heap_free(dd->heap, dd->dai_spec_config); @@ -1964,16 +1976,15 @@ static int dai_ts_stop_op(struct comp_dev *dev) uint32_t dai_get_init_delay_ms(struct dai *dai) { const struct dai_properties *props; - k_spinlock_key_t key; uint32_t init_delay; if (!dai) return 0; - key = k_spin_lock(&dai->lock); + k_mutex_lock(dai->lock, K_FOREVER); props = dai_get_properties(dai->dev, 0, 0); init_delay = props->reg_init_delay; - k_spin_unlock(&dai->lock, key); + k_mutex_unlock(dai->lock); return init_delay; } diff --git a/src/include/sof/lib/dai-zephyr.h b/src/include/sof/lib/dai-zephyr.h index 716605e157a3..0b55cc5e1845 100644 --- a/src/include/sof/lib/dai-zephyr.h +++ b/src/include/sof/lib/dai-zephyr.h @@ -52,7 +52,10 @@ struct dai { uint32_t dma_dev; const struct device *dev; const struct dai_data *dd; - struct k_spinlock lock; /* protect properties */ + struct k_mutex *lock; /* protect properties */ +#ifndef CONFIG_SOF_USERSPACE_LL + struct k_mutex lock_obj; +#endif }; union hdalink_cfg { From 10469f937f48111042ca4702f3c2c2bbf27a4925 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Wed, 18 Feb 2026 15:49:22 +0200 Subject: [PATCH 036/106] audio: dai-zephyr: migrate to use dai_get_properties_copy() Modify code to allocate DAI properties object on stack and use dai_get_properties_copy(). This is required when DAI code is run in user-space and a syscall is needed to talk to the DAI driver. It's not possible to return a pointer to kernel memory, so instead data needs to be copied to caller stack. Signed-off-by: Kai Vehmanen --- src/audio/dai-zephyr.c | 54 +++++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/src/audio/dai-zephyr.c b/src/audio/dai-zephyr.c index 990ec3848165..450926594db6 100644 --- a/src/audio/dai-zephyr.c +++ b/src/audio/dai-zephyr.c @@ -222,58 +222,62 @@ __cold int dai_set_config(struct dai *dai, struct ipc_config_dai *common_config, /* called from ipc/ipc3/dai.c */ int dai_get_handshake(struct dai *dai, int direction, int stream_id) { - const struct dai_properties *props; - int hs_id; + struct dai_properties props; + int ret; k_mutex_lock(dai->lock, K_FOREVER); - props = dai_get_properties(dai->dev, direction, stream_id); - hs_id = props->dma_hs_id; + ret = dai_get_properties_copy(dai->dev, direction, stream_id, &props); k_mutex_unlock(dai->lock); + if (ret < 0) + return ret; - return hs_id; + return props.dma_hs_id; } /* called from ipc/ipc3/dai.c and ipc/ipc4/dai.c */ int dai_get_fifo_depth(struct dai *dai, int direction) { - const struct dai_properties *props; - int fifo_depth; + struct dai_properties props; + int ret; if (!dai) return 0; k_mutex_lock(dai->lock, K_FOREVER); - props = dai_get_properties(dai->dev, direction, 0); - fifo_depth = props->fifo_depth; + ret = dai_get_properties_copy(dai->dev, direction, 0, &props); k_mutex_unlock(dai->lock); + if (ret < 0) + return 0; - return fifo_depth; + return props.fifo_depth; } int dai_get_stream_id(struct dai *dai, int direction) { - const struct dai_properties *props; - int stream_id; + struct dai_properties props; + int ret; k_mutex_lock(dai->lock, K_FOREVER); - props = dai_get_properties(dai->dev, direction, 0); - stream_id = props->stream_id; + ret = dai_get_properties_copy(dai->dev, direction, 0, &props); k_mutex_unlock(dai->lock); + if (ret < 0) + return ret; - return stream_id; + return props.stream_id; } static int dai_get_fifo(struct dai *dai, int direction, int stream_id) { - const struct dai_properties *props; - int fifo_address; + struct dai_properties props; + int ret; k_mutex_lock(dai->lock, K_FOREVER); - props = dai_get_properties(dai->dev, direction, stream_id); - fifo_address = props->fifo_address; + ret = dai_get_properties_copy(dai->dev, direction, stream_id, &props); k_mutex_unlock(dai->lock); + if (ret < 0) + return ret; - return fifo_address; + return props.fifo_address; } /* this is called by DMA driver every time descriptor has completed */ @@ -1975,15 +1979,17 @@ static int dai_ts_stop_op(struct comp_dev *dev) uint32_t dai_get_init_delay_ms(struct dai *dai) { - const struct dai_properties *props; - uint32_t init_delay; + struct dai_properties props; + uint32_t init_delay = 0; + int ret; if (!dai) return 0; k_mutex_lock(dai->lock, K_FOREVER); - props = dai_get_properties(dai->dev, 0, 0); - init_delay = props->reg_init_delay; + ret = dai_get_properties_copy(dai->dev, 0, 0, &props); + if (!ret) + init_delay = props.reg_init_delay; k_mutex_unlock(dai->lock); return init_delay; From e85ee0c332ac1d277798fd1d99ea02503ad45192 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Wed, 22 Apr 2026 15:16:04 +0300 Subject: [PATCH 037/106] dai: zephyr: replace k_mutex with sys_mutex for DAI lock - replace dynamically-allocated k_mutex *lock with embedded sys_mutex - remove k_object_alloc/k_thread_access_grant boilerplate - update 5 lock/unlock sites to sys_mutex_lock/unlock - remove k_object_free leak workaround in dai_common_free() Signed-off-by: Kai Vehmanen --- src/audio/dai-zephyr.c | 31 +++++++++++-------------------- src/include/sof/lib/dai-zephyr.h | 6 ++---- 2 files changed, 13 insertions(+), 24 deletions(-) diff --git a/src/audio/dai-zephyr.c b/src/audio/dai-zephyr.c index 450926594db6..6f2b35bac13e 100644 --- a/src/audio/dai-zephyr.c +++ b/src/audio/dai-zephyr.c @@ -225,9 +225,9 @@ int dai_get_handshake(struct dai *dai, int direction, int stream_id) struct dai_properties props; int ret; - k_mutex_lock(dai->lock, K_FOREVER); + sys_mutex_lock(&dai->lock, K_FOREVER); ret = dai_get_properties_copy(dai->dev, direction, stream_id, &props); - k_mutex_unlock(dai->lock); + sys_mutex_unlock(&dai->lock); if (ret < 0) return ret; @@ -243,9 +243,9 @@ int dai_get_fifo_depth(struct dai *dai, int direction) if (!dai) return 0; - k_mutex_lock(dai->lock, K_FOREVER); + sys_mutex_lock(&dai->lock, K_FOREVER); ret = dai_get_properties_copy(dai->dev, direction, 0, &props); - k_mutex_unlock(dai->lock); + sys_mutex_unlock(&dai->lock); if (ret < 0) return 0; @@ -257,9 +257,9 @@ int dai_get_stream_id(struct dai *dai, int direction) struct dai_properties props; int ret; - k_mutex_lock(dai->lock, K_FOREVER); + sys_mutex_lock(&dai->lock, K_FOREVER); ret = dai_get_properties_copy(dai->dev, direction, 0, &props); - k_mutex_unlock(dai->lock); + sys_mutex_unlock(&dai->lock); if (ret < 0) return ret; @@ -271,9 +271,9 @@ static int dai_get_fifo(struct dai *dai, int direction, int stream_id) struct dai_properties props; int ret; - k_mutex_lock(dai->lock, K_FOREVER); + sys_mutex_lock(&dai->lock, K_FOREVER); ret = dai_get_properties_copy(dai->dev, direction, stream_id, &props); - k_mutex_unlock(dai->lock); + sys_mutex_unlock(&dai->lock); if (ret < 0) return ret; @@ -527,12 +527,7 @@ __cold int dai_common_new(struct dai_data *dd, struct comp_dev *dev, return -ENODEV; } -#ifdef CONFIG_SOF_USERSPACE_LL - dd->dai->lock = k_object_alloc(K_OBJ_MUTEX); -#else - dd->dai->lock = &dd->dai->lock_obj; -#endif - k_mutex_init(dd->dai->lock); + sys_mutex_init(&dd->dai->lock); dma_sg_init(&dd->config.elem_array); dd->xrun = 0; @@ -661,10 +656,6 @@ __cold void dai_common_free(struct dai_data *dd) dai_release_llp_slot(dd); -#ifdef CONFIG_SOF_USERSPACE_LL - k_object_free(dd->dai->lock); -#endif - dai_put(dd->dai); sof_heap_free(dd->heap, dd->dai_spec_config); @@ -1986,11 +1977,11 @@ uint32_t dai_get_init_delay_ms(struct dai *dai) if (!dai) return 0; - k_mutex_lock(dai->lock, K_FOREVER); + sys_mutex_lock(&dai->lock, K_FOREVER); ret = dai_get_properties_copy(dai->dev, 0, 0, &props); if (!ret) init_delay = props.reg_init_delay; - k_mutex_unlock(dai->lock); + sys_mutex_unlock(&dai->lock); return init_delay; } diff --git a/src/include/sof/lib/dai-zephyr.h b/src/include/sof/lib/dai-zephyr.h index 0b55cc5e1845..34d077c92d7a 100644 --- a/src/include/sof/lib/dai-zephyr.h +++ b/src/include/sof/lib/dai-zephyr.h @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -52,10 +53,7 @@ struct dai { uint32_t dma_dev; const struct device *dev; const struct dai_data *dd; - struct k_mutex *lock; /* protect properties */ -#ifndef CONFIG_SOF_USERSPACE_LL - struct k_mutex lock_obj; -#endif + struct sys_mutex lock; /* protect properties */ }; union hdalink_cfg { From 38e0eeabd2912dafe37ce5f066c716473bdf129c Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 19 Feb 2026 15:05:22 +0200 Subject: [PATCH 038/106] (---section dai-zephyr STOP) From dc9e7a8b8b99ca224be7fa6bc5c4ce1d5ce9b349 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Tue, 3 Mar 2026 15:54:12 +0200 Subject: [PATCH 039/106] (---section audio module infra START) From cc54471a9b24e515171531cafdff9eba1a44d307 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 12 Feb 2026 20:38:50 +0200 Subject: [PATCH 040/106] audio: module_adapter: alloc from LL user heap if LL run in user When SOF is built with LL pipes in user-space, module adapter should allocate all resources from the LL user heap. Signed-off-by: Kai Vehmanen --- src/audio/module_adapter/module_adapter.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/audio/module_adapter/module_adapter.c b/src/audio/module_adapter/module_adapter.c index 9218b0df33ce..156c2545c1c7 100644 --- a/src/audio/module_adapter/module_adapter.c +++ b/src/audio/module_adapter/module_adapter.c @@ -111,7 +111,12 @@ static struct processing_module *module_adapter_mem_alloc(const struct comp_driv } mod_heap = &mod_heap_user->heap; } else { +#ifdef CONFIG_SOF_USERSPACE_LL + mod_heap = zephyr_ll_user_heap(); + comp_cl_dbg(drv, "using ll user heap for module"); +#else mod_heap = drv->user_heap; +#endif mod_heap_user = NULL; heap_size = 0; } From b01d846bef64e4c1a89e575c40aab01db6910d97 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 26 Feb 2026 17:11:56 +0200 Subject: [PATCH 041/106] audio: copier: export copier endpoint ops to user-space Allow the copier endpoint interface ops to be used also from user-space. Signed-off-by: Kai Vehmanen --- src/audio/copier/copier.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/audio/copier/copier.c b/src/audio/copier/copier.c index d2aefcb13be6..22063842786d 100644 --- a/src/audio/copier/copier.c +++ b/src/audio/copier/copier.c @@ -1189,7 +1189,7 @@ __cold static int copier_unbind(struct processing_module *mod, struct bind_info return 0; } -static struct module_endpoint_ops copier_endpoint_ops = { +static APP_TASK_DATA const struct module_endpoint_ops copier_endpoint_ops = { .get_total_data_processed = copier_get_processed_data, .position = copier_position, .dai_ts_config = copier_dai_ts_config_op, @@ -1200,7 +1200,7 @@ static struct module_endpoint_ops copier_endpoint_ops = { .trigger = copier_comp_trigger }; -static const struct module_interface copier_interface = { +static APP_TASK_DATA const struct module_interface copier_interface = { .init = copier_init, .prepare = copier_prepare, .process_audio_stream = copier_process, From beeb3e3e46dba99ae7882f1ae3dcef928ae0b1d7 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Tue, 3 Mar 2026 15:54:37 +0200 Subject: [PATCH 042/106] (---section schduler changes START) From 1ec82a7efcfab3033a327457a829ff2bcb984325 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 26 Feb 2026 17:42:13 +0200 Subject: [PATCH 043/106] schedule: zephyr_ll: add zephyr_ll_task_free() Add counterpart to zephyr_ll_task_alloc() to allow freeing the task with correct heap. Signed-off-by: Kai Vehmanen --- src/include/sof/schedule/ll_schedule_domain.h | 1 + src/schedule/zephyr_ll.c | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/include/sof/schedule/ll_schedule_domain.h b/src/include/sof/schedule/ll_schedule_domain.h index dc28e43c7461..d3a8200d0085 100644 --- a/src/include/sof/schedule/ll_schedule_domain.h +++ b/src/include/sof/schedule/ll_schedule_domain.h @@ -99,6 +99,7 @@ static inline struct ll_schedule_domain *dma_domain_get(void) #ifdef CONFIG_SOF_USERSPACE_LL struct task *zephyr_ll_task_alloc(void); +void zephyr_ll_task_free(struct task *task); struct k_heap *zephyr_ll_user_heap(void); void zephyr_ll_user_resources_init(void); #endif /* CONFIG_SOF_USERSPACE_LL */ diff --git a/src/schedule/zephyr_ll.c b/src/schedule/zephyr_ll.c index 654948e8377f..d9e51e89e6dc 100644 --- a/src/schedule/zephyr_ll.c +++ b/src/schedule/zephyr_ll.c @@ -404,7 +404,7 @@ static int zephyr_ll_task_schedule_after(void *data, struct task *task, uint64_t * This is synchronous - after this returns the object can be destroyed! * Assertion: under Zephyr this is always called from a thread context! */ -static int zephyr_ll_task_free(void *data, struct task *task) +static int zephyr_ll_task_sched_free(void *data, struct task *task) { struct zephyr_ll *sch = data; uint32_t flags; @@ -516,7 +516,7 @@ static const struct scheduler_ops zephyr_ll_ops = { .schedule_task = zephyr_ll_task_schedule, .schedule_task_before = zephyr_ll_task_schedule_before, .schedule_task_after = zephyr_ll_task_schedule_after, - .schedule_task_free = zephyr_ll_task_free, + .schedule_task_free = zephyr_ll_task_sched_free, .schedule_task_cancel = zephyr_ll_task_cancel, .scheduler_free = zephyr_ll_scheduler_free, }; @@ -527,6 +527,12 @@ struct task *zephyr_ll_task_alloc(void) return sof_heap_alloc(zephyr_ll_user_heap(), SOF_MEM_FLAG_USER, sizeof(struct task), sizeof(void *)); } + +void zephyr_ll_task_free(struct task *task) +{ + sof_heap_free(zephyr_ll_user_heap(), task); +} + #endif /* CONFIG_SOF_USERSPACE_LL */ int zephyr_ll_task_init(struct task *task, From a7f8d6eac889f0c3c79083e1b1a505a44ce2773c Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 26 Feb 2026 17:43:48 +0200 Subject: [PATCH 044/106] schedule: zephyr_ll: add zephyr_ll_grant_access() Add function zephyr_ll_grant_access() to allow other threads to access the scheduler mutex. This is needed if work is submitted from other threads to the scheduler. Signed-off-by: Kai Vehmanen --- src/include/sof/schedule/ll_schedule_domain.h | 1 + src/schedule/zephyr_ll.c | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/src/include/sof/schedule/ll_schedule_domain.h b/src/include/sof/schedule/ll_schedule_domain.h index d3a8200d0085..ec1e093879f1 100644 --- a/src/include/sof/schedule/ll_schedule_domain.h +++ b/src/include/sof/schedule/ll_schedule_domain.h @@ -102,6 +102,7 @@ struct task *zephyr_ll_task_alloc(void); void zephyr_ll_task_free(struct task *task); struct k_heap *zephyr_ll_user_heap(void); void zephyr_ll_user_resources_init(void); +void zephyr_ll_grant_access(struct k_thread *thread); #endif /* CONFIG_SOF_USERSPACE_LL */ static inline struct ll_schedule_domain *domain_init diff --git a/src/schedule/zephyr_ll.c b/src/schedule/zephyr_ll.c index d9e51e89e6dc..a541057d3fab 100644 --- a/src/schedule/zephyr_ll.c +++ b/src/schedule/zephyr_ll.c @@ -533,6 +533,13 @@ void zephyr_ll_task_free(struct task *task) sof_heap_free(zephyr_ll_user_heap(), task); } +void zephyr_ll_grant_access(struct k_thread *thread) +{ + struct zephyr_ll *ll_sch = (struct zephyr_ll *)scheduler_get_data(SOF_SCHEDULE_LL_TIMER); + + k_thread_access_grant(thread, ll_sch->lock); +} + #endif /* CONFIG_SOF_USERSPACE_LL */ int zephyr_ll_task_init(struct task *task, From 41bde5d26c46aca836d59eab351cf967d6c0f438 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Wed, 4 Mar 2026 19:29:08 +0200 Subject: [PATCH 045/106] 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 2837278fc299..a0b833ec3a64 100644 --- a/src/schedule/zephyr_ll_user.c +++ b/src/schedule/zephyr_ll_user.c @@ -55,6 +55,7 @@ static struct k_heap *zephyr_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); @@ -62,6 +63,7 @@ static struct k_heap *zephyr_ll_heap_init(void) (void *)mem_partition.start, heap->heap.init_bytes, ret); if (ret) k_panic(); +#endif return heap; } From 69fa3ded401d8b8aea5a0262bbb8f83729add599 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 26 Feb 2026 17:45:16 +0200 Subject: [PATCH 046/106] schedule: allocate the scheduler objects with sof_heap_alloc Ensure the scheduler objects and lists of schedulers are allocated such that they can be used with both kernel and user-space LL scheduler implementations. Signed-off-by: Kai Vehmanen --- src/schedule/schedule.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/schedule/schedule.c b/src/schedule/schedule.c index 5e56b6c490e0..3b80ab4bf552 100644 --- a/src/schedule/schedule.c +++ b/src/schedule/schedule.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -47,15 +48,20 @@ int schedule_task_init(struct task *task, static void scheduler_register(struct schedule_data *scheduler) { struct schedulers **sch = arch_schedulers_get(); + struct k_heap *heap = NULL; +#ifdef CONFIG_SOF_USERSPACE_LL + heap = zephyr_ll_user_heap(); +#endif if (!*sch) { /* init schedulers list */ - *sch = rzalloc(SOF_MEM_FLAG_KERNEL, - sizeof(**sch)); + *sch = sof_heap_alloc(heap, SOF_MEM_FLAG_KERNEL, + sizeof(**sch), 0); if (!*sch) { tr_err(&sch_tr, "allocation failed"); return; } + memset(*sch, 0, sizeof(**sch)); list_init(&(*sch)->list); } @@ -65,16 +71,21 @@ static void scheduler_register(struct schedule_data *scheduler) void scheduler_init(int type, const struct scheduler_ops *ops, void *data) { struct schedule_data *sch; + struct k_heap *heap = NULL; +#ifdef CONFIG_SOF_USERSPACE_LL + heap = zephyr_ll_user_heap(); +#endif if (!ops || !ops->schedule_task || !ops->schedule_task_cancel || !ops->schedule_task_free) return; - sch = rzalloc(SOF_MEM_FLAG_KERNEL, sizeof(*sch)); + sch = sof_heap_alloc(heap, SOF_MEM_FLAG_KERNEL, sizeof(*sch), 0); if (!sch) { tr_err(&sch_tr, "allocation failed"); sof_panic(SOF_IPC_PANIC_IPC); } + memset(sch, 0, sizeof(*sch)); list_init(&sch->list); sch->type = type; sch->ops = ops; From 986e71246c59d5fc5ffcc57285dce689eb9ccb68 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 26 Feb 2026 17:47:29 +0200 Subject: [PATCH 047/106] WIP: schedule: limit user-LL to core0 Multiple temporary limitations to limit user LL scheduler to only work on core 0. This is mostly related to privileged cpu_get_id() that is used in many places to check the id of the core code is running on. This is not available to user-space at the moment, so temporary measures are needed. Note: The following commits for scheduler depend on this, so this commit cannot be moved to the end of the series currently. Signed-off-by: Kai Vehmanen --- src/schedule/zephyr_domain.c | 4 ++-- zephyr/schedule.c | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/schedule/zephyr_domain.c b/src/schedule/zephyr_domain.c index 6a5812353d9e..612d14dbdf38 100644 --- a/src/schedule/zephyr_domain.c +++ b/src/schedule/zephyr_domain.c @@ -294,7 +294,7 @@ static int zephyr_domain_register_user(struct ll_schedule_domain *domain, void (*handler)(void *arg), void *arg) { struct zephyr_domain *zephyr_domain = ll_sch_domain_get_pdata(domain); - int core = cpu_get_id(); + int core = 0; /* cpu_get_id(); */ struct zephyr_domain_thread *dt = zephyr_domain->domain_thread + core; char thread_name[] = "ll_thread0"; k_tid_t thread; @@ -407,7 +407,7 @@ static int zephyr_domain_unregister_user(struct ll_schedule_domain *domain, struct k_thread *zephyr_domain_thread_tid(struct ll_schedule_domain *domain) { struct zephyr_domain *zephyr_domain = ll_sch_domain_get_pdata(domain); - int core = cpu_get_id(); + int core = 0; /* cpu_get_id(); */ struct zephyr_domain_thread *dt = zephyr_domain->domain_thread + core; tr_dbg(&ll_tr, "entry"); diff --git a/zephyr/schedule.c b/zephyr/schedule.c index 75155b5d4913..886132e1c04f 100644 --- a/zephyr/schedule.c +++ b/zephyr/schedule.c @@ -21,6 +21,11 @@ static struct schedulers *_schedulers[CONFIG_CORE_COUNT]; */ struct schedulers **arch_schedulers_get(void) { + if (k_is_user_context()) { + printk("FIXME: using core0 scheduler\n"); + return _schedulers; + } + return _schedulers + cpu_get_id(); } EXPORT_SYMBOL(arch_schedulers_get); From ef6991973e45a6138d36f511168d7985be0ca85b Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 26 Feb 2026 17:59:32 +0200 Subject: [PATCH 048/106] zephyr: schedule: allow user-space to access scheduler list Make the scheduler list available to all user-space threads. Signed-off-by: Kai Vehmanen --- zephyr/schedule.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zephyr/schedule.c b/zephyr/schedule.c index 886132e1c04f..94623b2ed5db 100644 --- a/zephyr/schedule.c +++ b/zephyr/schedule.c @@ -10,10 +10,11 @@ #include #include #include +#include #include #include -static struct schedulers *_schedulers[CONFIG_CORE_COUNT]; +static APP_TASK_BSS struct schedulers *_schedulers[CONFIG_CORE_COUNT]; /** * Retrieves registered schedulers. From 64c99064e19dec478ac035e0f35bec910d6aa4f4 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 26 Feb 2026 18:00:10 +0200 Subject: [PATCH 049/106] zephyr: wrapper: modify platform_dai_wallclock() for user-space Don't use sof_cycle_get_64() if SOF built for user-space LL. Signed-off-by: Kai Vehmanen --- zephyr/wrapper.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/zephyr/wrapper.c b/zephyr/wrapper.c index ede523024f59..f814c7c52beb 100644 --- a/zephyr/wrapper.c +++ b/zephyr/wrapper.c @@ -265,7 +265,11 @@ void platform_dai_timestamp(struct comp_dev *dai, /* get current wallclock for componnent */ void platform_dai_wallclock(struct comp_dev *dai, uint64_t *wallclock) { +#ifndef CONFIG_SOF_USERSPACE_LL *wallclock = sof_cycle_get_64(); +#else + *wallclock = k_uptime_get(); +#endif } /* From 8d7bb6c8d7d92aed42a049140ef80310392802cf Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Fri, 27 Feb 2026 16:59:02 +0200 Subject: [PATCH 050/106] schedule: add scheduler_init_context() and scheduler_free_context() Add two optional ops that allow the schedule.h user to get access to the thread context that will be used for scheduling. This is critical when the callbacks are run in user-space context and schedule.h client needs to grant access to objects like locks to the callback thread. Signed-off-by: Kai Vehmanen --- src/include/sof/schedule/schedule.h | 54 +++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/src/include/sof/schedule/schedule.h b/src/include/sof/schedule/schedule.h index bbdcbbecf3b4..9925e8d9b273 100644 --- a/src/include/sof/schedule/schedule.h +++ b/src/include/sof/schedule/schedule.h @@ -158,6 +158,25 @@ struct scheduler_ops { * This operation is optional. */ int (*scheduler_restore)(void *data); + + /** + * Initializes context + * @param data Private data of selected scheduler. + * @param task task that needs to be scheduled + * @return thread that will be used to run the scheduled task + * + * This operation is optional. + */ + struct k_thread *(*scheduler_init_context)(void *data, struct task *task); + + /** + * Frees scheduler context + * @param data Private data of selected scheduler. + * + * This operation is optional. + */ + void (*scheduler_free_context)(void *data); + }; /** \brief Holds information about scheduler. */ @@ -379,6 +398,41 @@ static inline int schedulers_restore(void) return 0; } + +/** See scheduler_ops::scheduler_init_context */ +static inline struct k_thread *scheduler_init_context(struct task *task) +{ + struct schedulers *schedulers = *arch_schedulers_get(); + struct schedule_data *sch; + struct list_item *slist; + + assert(schedulers); + + list_for_item(slist, &schedulers->list) { + sch = container_of(slist, struct schedule_data, list); + if (sch->ops->scheduler_init_context) + return sch->ops->scheduler_init_context(sch->data, task); + } + + return 0; +} + +/** See scheduler_ops::scheduler_free_context */ +static inline void scheduler_free_context(void) +{ + struct schedulers *schedulers = *arch_schedulers_get(); + struct schedule_data *sch; + struct list_item *slist; + + assert(schedulers); + + list_for_item(slist, &schedulers->list) { + sch = container_of(slist, struct schedule_data, list); + if (sch->ops->scheduler_free_context) + sch->ops->scheduler_free_context(sch->data); + } +} + /** * Initializes scheduling task. * @param task Task to be initialized. From e50f9027c6d13093a7e8a4e0550eb7852ac6b173 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 26 Feb 2026 18:02:34 +0200 Subject: [PATCH 051/106] schedule: zephyr_ll: implement scheduler_init_context() Implement the optional scheduler_init_context() and scheduler_free_context() ops for user-space LL scheduler. Signed-off-by: Kai Vehmanen --- src/schedule/zephyr_domain.c | 12 ++++++--- src/schedule/zephyr_ll.c | 48 +++++++++++++++++++++++++++--------- 2 files changed, 46 insertions(+), 14 deletions(-) diff --git a/src/schedule/zephyr_domain.c b/src/schedule/zephyr_domain.c index 612d14dbdf38..0cf96ce0a1e5 100644 --- a/src/schedule/zephyr_domain.c +++ b/src/schedule/zephyr_domain.c @@ -294,18 +294,24 @@ static int zephyr_domain_register_user(struct ll_schedule_domain *domain, void (*handler)(void *arg), void *arg) { struct zephyr_domain *zephyr_domain = ll_sch_domain_get_pdata(domain); - int core = 0; /* cpu_get_id(); */ - struct zephyr_domain_thread *dt = zephyr_domain->domain_thread + core; + struct zephyr_domain_thread *dt; char thread_name[] = "ll_thread0"; k_tid_t thread; + int core; tr_dbg(&ll_tr, "entry"); + if (task->core < 0 || task->core >= CONFIG_CORE_COUNT) + return -EINVAL; + + dt = zephyr_domain->domain_thread + task->core; + /* domain work only needs registered once on each core */ if (dt->handler) return 0; - __ASSERT_NO_MSG(task->core == core); + /* safety check executed in kernel mode */ + __ASSERT_NO_MSG(cpu_get_id() == core); dt->handler = handler; dt->arg = arg; diff --git a/src/schedule/zephyr_ll.c b/src/schedule/zephyr_ll.c index a541057d3fab..5babac8c3d25 100644 --- a/src/schedule/zephyr_ll.c +++ b/src/schedule/zephyr_ll.c @@ -361,17 +361,7 @@ static int zephyr_ll_task_schedule_common(struct zephyr_ll *sch, struct task *ta ret = domain_register(sch->ll_domain, task, &schedule_ll_callback, sch); if (ret < 0) - tr_err(&ll_tr, "cannot register domain %d", - ret); - -#if CONFIG_SOF_USERSPACE_LL - k_thread_access_grant(zephyr_domain_thread_tid(sch->ll_domain), sch->lock); - - tr_dbg(&ll_tr, "granting access to lock %p for thread %p", sch->lock, - zephyr_domain_thread_tid(sch->ll_domain)); - tr_dbg(&ll_tr, "granting access to domain lock %p for thread %p", &sch->ll_domain->lock, - zephyr_domain_thread_tid(sch->ll_domain)); -#endif + tr_err(&ll_tr, "cannot register domain %d", ret); return 0; } @@ -512,6 +502,38 @@ static void zephyr_ll_scheduler_free(void *data, uint32_t flags) sch->n_tasks); } +#if CONFIG_SOF_USERSPACE_LL +struct k_thread *zephyr_ll_init_context(void *data, struct task *task) +{ + struct zephyr_ll *sch = data; + int ret; + + ret = domain_register(sch->ll_domain, task, &schedule_ll_callback, sch); + if (ret < 0) { + tr_err(&ll_tr, "cannot init_context %d", ret); + return NULL; + } + + if (!k_is_user_context()) { + k_thread_access_grant(zephyr_domain_thread_tid(sch->ll_domain), sch->lock); + + tr_dbg(&ll_tr, "granting access to lock %p for thread %p", sch->lock, + zephyr_domain_thread_tid(sch->ll_domain)); + tr_dbg(&ll_tr, "granting access to domain lock %p for thread %p", &sch->ll_domain->lock, + zephyr_domain_thread_tid(sch->ll_domain)); + } + + return zephyr_domain_thread_tid(sch->ll_domain); +} + +void zephyr_ll_free_context(void *data) +{ + struct zephyr_ll *sch = data; + + (void *)sch; +} +#endif + static const struct scheduler_ops zephyr_ll_ops = { .schedule_task = zephyr_ll_task_schedule, .schedule_task_before = zephyr_ll_task_schedule_before, @@ -519,6 +541,10 @@ static const struct scheduler_ops zephyr_ll_ops = { .schedule_task_free = zephyr_ll_task_sched_free, .schedule_task_cancel = zephyr_ll_task_cancel, .scheduler_free = zephyr_ll_scheduler_free, +#if CONFIG_SOF_USERSPACE_LL + .scheduler_init_context = zephyr_ll_init_context, + .scheduler_free_context = zephyr_ll_free_context, +#endif }; #if CONFIG_SOF_USERSPACE_LL From 885065e2abe4f2d646321efeb8dc3e66fe268e28 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Wed, 4 Mar 2026 19:25:43 +0200 Subject: [PATCH 052/106] schedule: ll_schedule_domain: add domain_thread_init/free ops Add new domain ops that are called from privileged context and are used to set up resources like threads and initialize other kernel objects. Signed-off-by: Kai Vehmanen --- src/include/sof/schedule/ll_schedule_domain.h | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/include/sof/schedule/ll_schedule_domain.h b/src/include/sof/schedule/ll_schedule_domain.h index ec1e093879f1..08c4626ef5a6 100644 --- a/src/include/sof/schedule/ll_schedule_domain.h +++ b/src/include/sof/schedule/ll_schedule_domain.h @@ -43,6 +43,20 @@ struct ll_schedule_domain_ops { void (*handler)(void *arg), void *arg); int (*domain_unregister)(struct ll_schedule_domain *domain, struct task *task, uint32_t num_tasks); +#if CONFIG_SOF_USERSPACE_LL + /* + * Initialize the scheduling thread and perform all privileged setup + * (thread creation, timer init, access grants). Called once from + * kernel context before any user-space domain_register() calls. + */ + int (*domain_thread_init)(struct ll_schedule_domain *domain, + struct task *task); + /* Free resources acquired by domain_thread_init(). Called from + * kernel context when the scheduling context is being torn down. + */ + void (*domain_thread_free)(struct ll_schedule_domain *domain, + uint32_t num_tasks); +#endif void (*domain_enable)(struct ll_schedule_domain *domain, int core); void (*domain_disable)(struct ll_schedule_domain *domain, int core); #if CONFIG_CROSS_CORE_STREAM @@ -179,6 +193,31 @@ static inline void domain_task_cancel(struct ll_schedule_domain *domain, domain->ops->domain_task_cancel(domain, task); } +#if CONFIG_SOF_USERSPACE_LL +/* + * Initialize the scheduling thread and do all privileged setup. + * Must be called from kernel context before user-space tasks register. + */ +static inline int domain_thread_init(struct ll_schedule_domain *domain, + struct task *task) +{ + assert(domain->ops->domain_thread_init); + + return domain->ops->domain_thread_init(domain, task); +} + +/* + * Free resources acquired by domain_thread_init(). + * Must be called from kernel context. + */ +static inline void domain_thread_free(struct ll_schedule_domain *domain, + uint32_t num_tasks) +{ + if (domain->ops->domain_thread_free) + domain->ops->domain_thread_free(domain, num_tasks); +} +#endif + static inline int domain_register(struct ll_schedule_domain *domain, struct task *task, void (*handler)(void *arg), void *arg) From 0f26b804f72ea4e44029f2389c02908ec3e8f7f3 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Wed, 4 Mar 2026 19:27:31 +0200 Subject: [PATCH 053/106] schedule: zephyr_ll: implement thread_init/free domain ops Implement the new domain_thread_init/free() ops for the Zephyr LL scheduler implementation. Move all privileged operations to these methods. After this change, the domain_register() and domain_unregister() are now safe to call from user-space context. Signed-off-by: Kai Vehmanen --- src/schedule/zephyr_domain.c | 159 +++++++++++++++++++++++++---------- src/schedule/zephyr_ll.c | 10 ++- 2 files changed, 122 insertions(+), 47 deletions(-) diff --git a/src/schedule/zephyr_domain.c b/src/schedule/zephyr_domain.c index 0cf96ce0a1e5..70631ab2cf76 100644 --- a/src/schedule/zephyr_domain.c +++ b/src/schedule/zephyr_domain.c @@ -126,7 +126,8 @@ static void zephyr_domain_thread_fn(void *p1, void *p2, void *p3) } #endif - dt->handler(dt->arg); + if (dt->handler) + dt->handler(dt->arg); #ifdef CONFIG_SCHEDULE_LL_STATS_LOG cycles1 = k_cycle_get_32(); @@ -287,11 +288,14 @@ static int zephyr_domain_unregister(struct ll_schedule_domain *domain, #else /* CONFIG_SOF_USERSPACE_LL */ -/* User-space implementation for register/unregister */ - -static int zephyr_domain_register_user(struct ll_schedule_domain *domain, - struct task *task, - void (*handler)(void *arg), void *arg) +/* + * Privileged thread initialization for userspace LL scheduling. + * Creates the scheduling thread, sets up timer, grants access to kernel + * objects. Must be called from kernel context before any user-space + * domain_register() calls. + */ +static int zephyr_domain_thread_init(struct ll_schedule_domain *domain, + struct task *task) { struct zephyr_domain *zephyr_domain = ll_sch_domain_get_pdata(domain); struct zephyr_domain_thread *dt; @@ -299,55 +303,50 @@ static int zephyr_domain_register_user(struct ll_schedule_domain *domain, k_tid_t thread; int core; - tr_dbg(&ll_tr, "entry"); + tr_dbg(&ll_tr, "thread_init entry"); if (task->core < 0 || task->core >= CONFIG_CORE_COUNT) return -EINVAL; - dt = zephyr_domain->domain_thread + task->core; + core = task->core; + dt = zephyr_domain->domain_thread + core; - /* domain work only needs registered once on each core */ - if (dt->handler) + /* thread only needs to be created once per core */ + if (dt->ll_thread) return 0; - /* safety check executed in kernel mode */ - __ASSERT_NO_MSG(cpu_get_id() == core); - - dt->handler = handler; - dt->arg = arg; + dt->handler = NULL; /* 10 is rather random, we better not accumulate 10 missed timer interrupts */ k_sem_init(dt->sem, 0, 10); thread_name[sizeof(thread_name) - 2] = '0' + core; + /* Allocate thread structure dynamically */ + dt->ll_thread = k_object_alloc(K_OBJ_THREAD); if (!dt->ll_thread) { - /* Allocate thread structure dynamically */ - dt->ll_thread = k_object_alloc(K_OBJ_THREAD); - if (!dt->ll_thread) { - tr_err(&ll_tr, "Failed to allocate thread object for core %d", core); - dt->handler = NULL; - dt->arg = NULL; - return -ENOMEM; - } + tr_err(&ll_tr, "Failed to allocate thread object for core %d", core); + dt->handler = NULL; + dt->arg = NULL; + return -ENOMEM; + } - thread = k_thread_create(dt->ll_thread, ll_sched_stack[core], ZEPHYR_LL_STACK_SIZE, - zephyr_domain_thread_fn, zephyr_domain, - INT_TO_POINTER(core), NULL, CONFIG_LL_THREAD_PRIORITY, - K_USER, K_FOREVER); + thread = k_thread_create(dt->ll_thread, ll_sched_stack[core], ZEPHYR_LL_STACK_SIZE, + zephyr_domain_thread_fn, zephyr_domain, + INT_TO_POINTER(core), NULL, CONFIG_LL_THREAD_PRIORITY, + K_USER, K_FOREVER); - k_thread_cpu_mask_clear(thread); - k_thread_cpu_mask_enable(thread, core); - k_thread_name_set(thread, thread_name); + k_thread_cpu_mask_clear(thread); + k_thread_cpu_mask_enable(thread, core); + k_thread_name_set(thread, thread_name); - k_mem_domain_add_thread(zephyr_ll_mem_domain(), thread); - k_thread_access_grant(thread, dt->sem, domain->lock, zephyr_domain->timer); - user_grant_dai_access_all(thread); - user_grant_dma_access_all(thread); - tr_dbg(&ll_tr, "granted LL access to thread %p (core %d)", thread, core); + k_mem_domain_add_thread(zephyr_ll_mem_domain(), thread); + k_thread_access_grant(thread, dt->sem, domain->lock, zephyr_domain->timer); + user_grant_dai_access_all(thread); + user_grant_dma_access_all(thread); + tr_dbg(&ll_tr, "granted LL access to thread %p (core %d)", thread, core); - k_thread_start(thread); - } + k_thread_start(thread); k_mutex_lock(domain->lock, K_FOREVER); if (!k_timer_user_data_get(zephyr_domain->timer)) { @@ -370,6 +369,43 @@ static int zephyr_domain_register_user(struct ll_schedule_domain *domain, return 0; } +/* + * User-space register: bookkeeping only. The privileged thread setup has + * already been done by domain_thread_init() called from kernel context. + */ +static int zephyr_domain_register_user(struct ll_schedule_domain *domain, + struct task *task, + void (*handler)(void *arg), void *arg) +{ + struct zephyr_domain *zephyr_domain = ll_sch_domain_get_pdata(domain); + struct zephyr_domain_thread *dt; + int core; + + tr_dbg(&ll_tr, "register_user entry"); + + if (task->core < 0 || task->core >= CONFIG_CORE_COUNT) + return -EINVAL; + + core = task->core; + dt = zephyr_domain->domain_thread + core; + + if (!dt->ll_thread) { + tr_err(&ll_tr, "domain_thread_init() not called for core %d", core); + return -EINVAL; + } + + __ASSERT_NO_MSG(!dt->handler || dt->handler == handler); + if (dt->handler) + return 0; + + dt->handler = handler; + dt->arg = arg; + + tr_info(&ll_tr, "task registered on core %d", core); + + return 0; +} + static int zephyr_domain_unregister_user(struct ll_schedule_domain *domain, struct task *task, uint32_t num_tasks) { @@ -384,14 +420,6 @@ static int zephyr_domain_unregister_user(struct ll_schedule_domain *domain, k_mutex_lock(domain->lock, K_FOREVER); - if (!atomic_read(&domain->total_num_tasks)) { - /* Disable the watchdog */ - watchdog_disable(core); - - k_timer_stop(zephyr_domain->timer); - k_timer_user_data_set(zephyr_domain->timer, NULL); - } - zephyr_domain->domain_thread[core].handler = NULL; k_mutex_unlock(domain->lock); @@ -410,6 +438,45 @@ static int zephyr_domain_unregister_user(struct ll_schedule_domain *domain, return 0; } +/* + * Free resources acquired by zephyr_domain_thread_init(). + * Stops the timer, aborts the scheduling thread and frees the thread object. + * Must be called from kernel context. + */ +static void zephyr_domain_thread_free(struct ll_schedule_domain *domain, + uint32_t num_tasks) +{ + struct zephyr_domain *zephyr_domain = ll_sch_domain_get_pdata(domain); + int core = cpu_get_id(); + struct zephyr_domain_thread *dt = zephyr_domain->domain_thread + core; + + tr_dbg(&ll_tr, "thread_free entry, core %d, num_tasks %u", core, num_tasks); + + /* Still tasks on other cores, only clean up this core's thread */ + k_mutex_lock(domain->lock, K_FOREVER); + + if (!num_tasks && !atomic_read(&domain->total_num_tasks)) { + /* Last task globally: stop the timer and watchdog */ + watchdog_disable(core); + + k_timer_stop(zephyr_domain->timer); + k_timer_user_data_set(zephyr_domain->timer, NULL); + } + + dt->handler = NULL; + dt->arg = NULL; + + k_mutex_unlock(domain->lock); + + if (dt->ll_thread) { + k_thread_abort(dt->ll_thread); + k_object_free(dt->ll_thread); + dt->ll_thread = NULL; + } + + tr_info(&ll_tr, "thread_free done, core %d", core); +} + struct k_thread *zephyr_domain_thread_tid(struct ll_schedule_domain *domain) { struct zephyr_domain *zephyr_domain = ll_sch_domain_get_pdata(domain); @@ -452,6 +519,8 @@ APP_TASK_DATA static const struct ll_schedule_domain_ops zephyr_domain_ops = { #ifdef CONFIG_SOF_USERSPACE_LL .domain_register = zephyr_domain_register_user, .domain_unregister = zephyr_domain_unregister_user, + .domain_thread_init = zephyr_domain_thread_init, + .domain_thread_free = zephyr_domain_thread_free, #else .domain_register = zephyr_domain_register, .domain_unregister = zephyr_domain_unregister, diff --git a/src/schedule/zephyr_ll.c b/src/schedule/zephyr_ll.c index 5babac8c3d25..e61d420f5dba 100644 --- a/src/schedule/zephyr_ll.c +++ b/src/schedule/zephyr_ll.c @@ -508,7 +508,12 @@ struct k_thread *zephyr_ll_init_context(void *data, struct task *task) struct zephyr_ll *sch = data; int ret; - ret = domain_register(sch->ll_domain, task, &schedule_ll_callback, sch); + /* + * Use domain_thread_init() for privileged setup (thread creation, + * timer, access grants). domain_register() is now bookkeeping only + * and will be called later from user context when scheduling tasks. + */ + ret = domain_thread_init(sch->ll_domain, task); if (ret < 0) { tr_err(&ll_tr, "cannot init_context %d", ret); return NULL; @@ -530,7 +535,8 @@ void zephyr_ll_free_context(void *data) { struct zephyr_ll *sch = data; - (void *)sch; + tr_info(&ll_tr, "free the domain thread"); + domain_thread_free(sch->ll_domain, sch->n_tasks); } #endif From 87380544abd47df8f6bc5a4f8e69f570b881f0aa Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Tue, 31 Mar 2026 18:29:20 +0300 Subject: [PATCH 054/106] schedule: zephyr_ll: ISR check is not needed when LL in user-space No need to check whether we are running in ISR as this can never happen when LL scheduler is run in user-space. Signed-off-by: Kai Vehmanen --- src/schedule/zephyr_ll.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/schedule/zephyr_ll.c b/src/schedule/zephyr_ll.c index e61d420f5dba..801b3671ea67 100644 --- a/src/schedule/zephyr_ll.c +++ b/src/schedule/zephyr_ll.c @@ -403,10 +403,12 @@ static int zephyr_ll_task_sched_free(void *data, struct task *task) zephyr_ll_assert_core(sch); +#ifndef CONFIG_SOF_USERSPACE_LL if (k_is_in_isr()) { tr_err(&ll_tr, "cannot free tasks from interrupt context!"); return -EDEADLK; } +#endif zephyr_ll_lock(sch, &flags); From 52f4d71e5e7e5f59cba872c983269deed4059af3 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Tue, 31 Mar 2026 18:04:53 +0300 Subject: [PATCH 055/106] zephyr: userspace_helper: add new sys_user_heap_init() For user-space LL scheduler to implement full life-cycle for modules, including calls mod_free(). This requires that heap metadata is also part of the memory partition mapped to the user-thread. Implement a new sys_user_heap_init() that allocates both the heap buffer and its metadata, into single contiguous allocation, and returns the resulting object. This then allows caller to map both metadata and the heap to a user-space thread. Signed-off-by: Kai Vehmanen --- src/schedule/zephyr_ll_user.c | 2 +- zephyr/include/rtos/userspace_helper.h | 21 ++++++++++++++ zephyr/lib/userspace_helper.c | 39 ++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/src/schedule/zephyr_ll_user.c b/src/schedule/zephyr_ll_user.c index a0b833ec3a64..866689ab73a1 100644 --- a/src/schedule/zephyr_ll_user.c +++ b/src/schedule/zephyr_ll_user.c @@ -30,7 +30,7 @@ APP_TASK_DATA static struct k_heap *zephyr_ll_heap; static struct k_heap *zephyr_ll_heap_init(void) { - struct k_heap *heap = module_driver_heap_init(); + struct k_heap *heap = sys_user_heap_init(); struct k_mem_partition mem_partition; int ret; diff --git a/zephyr/include/rtos/userspace_helper.h b/zephyr/include/rtos/userspace_helper.h index 6951d6dcdd43..32e0f6290525 100644 --- a/zephyr/include/rtos/userspace_helper.h +++ b/zephyr/include/rtos/userspace_helper.h @@ -45,6 +45,27 @@ struct userspace_context; */ struct k_heap *module_driver_heap_init(void); +/** + * Initialize private processing module heap with embedded metadata. + * @return pointer to the k_heap structure. + * + * @note + * Unlike module_driver_heap_init(), this function embeds the k_heap + * struct at the start of the page-aligned backing buffer. The + * init_mem / init_bytes fields cover the full allocation, so a + * memory partition derived from them automatically includes the + * k_heap metadata (required for userspace syscall verification). + * Must be freed with sys_user_heap_remove(). + */ +struct k_heap *sys_user_heap_init(void); + +/** + * Free private processing module heap allocated by + * sys_user_heap_init(). + * @param mod_drv_heap pointer to the k_heap structure. + */ +void sys_user_heap_remove(struct k_heap *mod_drv_heap); + /** * Attach common userspace memory partition to a module memory domain. * @param dom - memory domain to attach the common partition to. diff --git a/zephyr/lib/userspace_helper.c b/zephyr/lib/userspace_helper.c index c7c361295269..3ce8e81f72b8 100644 --- a/zephyr/lib/userspace_helper.c +++ b/zephyr/lib/userspace_helper.c @@ -69,6 +69,45 @@ void module_driver_heap_remove(struct k_heap *mod_drv_heap) } } +struct k_heap *sys_user_heap_init(void) +{ + const size_t prefix = ALIGN_UP(sizeof(struct k_heap), 4); + const size_t total = prefix + USER_MOD_HEAP_SIZE; + + /* + * Allocate a single page-aligned buffer for both the k_heap + * metadata and the heap backing memory. Placing the k_heap + * struct inside the same allocation means the memory partition + * (which uses init_mem / init_bytes) automatically covers the + * k_heap struct, making it accessible from userspace syscall + * verification wrappers such as z_vrfy_mod_free(). + */ + void *mem = rballoc_align(SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, total, + CONFIG_MM_DRV_PAGE_SIZE); + if (!mem) + return NULL; + + struct k_heap *mod_drv_heap = (struct k_heap *)mem; + void *heap_buf = (uint8_t *)mem + prefix; + + k_heap_init(mod_drv_heap, heap_buf, USER_MOD_HEAP_SIZE); + + /* init_mem / init_bytes track the full allocation so that + * partition setup and sys_user_heap_remove() + * cover and free the entire region including the k_heap struct. + */ + mod_drv_heap->heap.init_mem = mem; + mod_drv_heap->heap.init_bytes = total; + + return mod_drv_heap; +} + +void sys_user_heap_remove(struct k_heap *mod_drv_heap) +{ + if (mod_drv_heap) + rfree(mod_drv_heap); +} + void *user_stack_allocate(size_t stack_size, uint32_t options) { return k_thread_stack_alloc(stack_size, options & K_USER); From 037faa1604d57857dba442dc757219cee2c092d4 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Tue, 3 Mar 2026 15:54:51 +0200 Subject: [PATCH 056/106] (---section schduler changes END) From 8ea3917defe431fd824df613ce3910180ae0c4cb Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Wed, 25 Mar 2026 19:31:48 +0200 Subject: [PATCH 057/106] (---section audio-user PRs START) From 398599179c04dfa9151f1333fe0f34d5f6003398 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Tue, 24 Mar 2026 18:19:26 +0200 Subject: [PATCH 058/106] schedule: zephyr_domain: use a different thread name for user LL When LL scheduler is run in user-space, use a different Zephyr thread name. Signed-off-by: Kai Vehmanen --- src/schedule/zephyr_domain.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/schedule/zephyr_domain.c b/src/schedule/zephyr_domain.c index 70631ab2cf76..f77ef40a1933 100644 --- a/src/schedule/zephyr_domain.c +++ b/src/schedule/zephyr_domain.c @@ -299,7 +299,7 @@ static int zephyr_domain_thread_init(struct ll_schedule_domain *domain, { struct zephyr_domain *zephyr_domain = ll_sch_domain_get_pdata(domain); struct zephyr_domain_thread *dt; - char thread_name[] = "ll_thread0"; + char thread_name[] = "userll_thread0"; k_tid_t thread; int core; From 137f2dc3ba6abc30309ea4029004c399cb9e0124 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Fri, 20 Mar 2026 21:39:37 +0200 Subject: [PATCH 059/106] WIP: audio: module_adapter: use correct heap when freeing Signed-off-by: Kai Vehmanen --- src/audio/module_adapter/module_adapter.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/audio/module_adapter/module_adapter.c b/src/audio/module_adapter/module_adapter.c index 156c2545c1c7..7865ff7161c6 100644 --- a/src/audio/module_adapter/module_adapter.c +++ b/src/audio/module_adapter/module_adapter.c @@ -177,6 +177,12 @@ static void module_adapter_mem_free(struct processing_module *mod) sof_heap_free(mod_heap, mod->priv.cfg.input_pins); #endif sof_heap_free(mod_heap, mod->dev); + LOG_INF("mod"); +#ifdef CONFIG_SOF_USERSPACE_LL + mod_heap = zephyr_ll_user_heap(); + comp_cl_dbg(drv, "using ll user heap for module free"); +#endif + comp_cl_info(drv, "free mod %p with heap %p", mod, mod_heap); sof_heap_free(mod_heap, mod); if (domain == COMP_PROCESSING_DOMAIN_DP) { struct dp_heap_user *mod_heap_user = container_of(mod_heap, struct dp_heap_user, From 05408b79f83758a5f65a15f6e61f726786c64796 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Fri, 20 Mar 2026 21:40:57 +0200 Subject: [PATCH 060/106] coherent: disable core debug checks for user-space builds The COHERENT_CHECK_NONSHARED_CORES debug macros call cpu_get_id() which invokes arch_proc_id() - a privileged hardware register read that faults in user-space context. Disable the entire debug block at compile time when CONFIG_SOF_USERSPACE_LL is enabled. This also fixes the same latent issue in CORE_CHECK_STRUCT and CORE_CHECK_STRUCT_INIT. Signed-off-by: Kai Vehmanen --- src/include/sof/coherent.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/include/sof/coherent.h b/src/include/sof/coherent.h index 172e45b4ed92..ba6b8d8c7e52 100644 --- a/src/include/sof/coherent.h +++ b/src/include/sof/coherent.h @@ -86,8 +86,8 @@ STATIC_ASSERT(sizeof(struct coherent) <= DCACHE_LINE_SIZE, DCACHE_LINE_SIZE_too #define ADDR_IS_COHERENT(_c) #endif -/* debug sharing amongst cores */ -#ifdef COHERENT_CHECK_NONSHARED_CORES +/* debug sharing amongst cores - not available in user-space builds */ +#if defined(COHERENT_CHECK_NONSHARED_CORES) && !defined(CONFIG_SOF_USERSPACE_LL) #define CORE_CHECK_STRUCT_FIELD uint32_t __core; bool __is_shared #define CORE_CHECK_STRUCT_INIT(_c, is_shared) { (_c)->__core = cpu_get_id(); \ From 8bbafa5d687482f39f5025deb250fdae57c451c5 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Tue, 24 Mar 2026 18:16:12 +0200 Subject: [PATCH 061/106] audio: place component driver list in user-space accessible partition Annotate the static comp_driver_list with APP_SYSUSER_BSS so that it is placed in the sysuser memory partition. This makes the component driver registry accessible from user-space threads when CONFIG_SOF_USERSPACE_LL is enabled. Signed-off-by: Kai Vehmanen --- src/audio/component.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/audio/component.c b/src/audio/component.c index 058bf733ae5c..dd9a6e32835b 100644 --- a/src/audio/component.c +++ b/src/audio/component.c @@ -36,7 +36,7 @@ LOG_MODULE_REGISTER(component, CONFIG_SOF_LOG_LEVEL); -static SHARED_DATA struct comp_driver_list cd; +static APP_SYSUSER_BSS SHARED_DATA struct comp_driver_list cd; SOF_DEFINE_REG_UUID(component); From 4a996d1abcca32d56856ce1b5e6a3c4bc780bbde Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Tue, 24 Mar 2026 18:17:19 +0200 Subject: [PATCH 062/106] audio: module_adapter: make adapter buffering user-space compatible Replace all calls to rzalloc() and rballoc() with calls to sof_heap_alloc() with heap set based on how SOF is built. If audio pipelines are run fully in user-space, the sof_sys_user_heap_get() should be used. Signed-off-by: Kai Vehmanen --- src/audio/module_adapter/module_adapter.c | 33 ++++++++++++++--------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/audio/module_adapter/module_adapter.c b/src/audio/module_adapter/module_adapter.c index 7865ff7161c6..860e518bb6b3 100644 --- a/src/audio/module_adapter/module_adapter.c +++ b/src/audio/module_adapter/module_adapter.c @@ -519,11 +519,13 @@ int module_adapter_prepare(struct comp_dev *dev) /* allocate memory for input buffers */ if (mod->max_sources) { mod->input_buffers = - rzalloc(memory_flags, sizeof(*mod->input_buffers) * mod->max_sources); + sof_heap_alloc(sof_sys_user_heap_get(), memory_flags, + sizeof(*mod->input_buffers) * mod->max_sources, 0); if (!mod->input_buffers) { comp_err(dev, "failed to allocate input buffers"); return -ENOMEM; } + memset(mod->input_buffers, 0, sizeof(*mod->input_buffers) * mod->max_sources); } else { mod->input_buffers = NULL; } @@ -531,12 +533,14 @@ int module_adapter_prepare(struct comp_dev *dev) /* allocate memory for output buffers */ if (mod->max_sinks) { mod->output_buffers = - rzalloc(memory_flags, sizeof(*mod->output_buffers) * mod->max_sinks); + sof_heap_alloc(sof_sys_user_heap_get(), memory_flags, + sizeof(*mod->output_buffers) * mod->max_sources, 0); if (!mod->output_buffers) { comp_err(dev, "failed to allocate output buffers"); ret = -ENOMEM; goto in_out_free; } + memset(mod->input_buffers, 0, sizeof(*mod->output_buffers) * mod->max_sources); } else { mod->output_buffers = NULL; } @@ -597,7 +601,8 @@ int module_adapter_prepare(struct comp_dev *dev) size_t size = MAX(mod->deep_buff_bytes, mod->period_bytes); list_for_item(blist, &dev->bsource_list) { - mod->input_buffers[i].data = rballoc(memory_flags, size); + mod->input_buffers[i].data = sof_heap_alloc(sof_sys_user_heap_get(), + memory_flags, size, 0); if (!mod->input_buffers[i].data) { comp_err(mod->dev, "Failed to alloc input buffer data"); ret = -ENOMEM; @@ -609,7 +614,9 @@ int module_adapter_prepare(struct comp_dev *dev) /* allocate memory for output buffer data */ i = 0; list_for_item(blist, &dev->bsink_list) { - mod->output_buffers[i].data = rballoc(memory_flags, md->mpd.out_buff_size); + mod->output_buffers[i].data = sof_heap_alloc(sof_sys_user_heap_get(), + memory_flags, + md->mpd.out_buff_size, 0); if (!mod->output_buffers[i].data) { comp_err(mod->dev, "Failed to alloc output buffer data"); ret = -ENOMEM; @@ -684,16 +691,16 @@ int module_adapter_prepare(struct comp_dev *dev) out_data_free: for (i = 0; i < mod->num_of_sinks; i++) - rfree(mod->output_buffers[i].data); + sof_heap_free(sof_sys_user_heap_get(), mod->output_buffers[i].data); in_data_free: for (i = 0; i < mod->num_of_sources; i++) - rfree(mod->input_buffers[i].data); + sof_heap_free(sof_sys_user_heap_get(), mod->input_buffers[i].data); in_out_free: - rfree(mod->output_buffers); + sof_heap_free(sof_sys_user_heap_get(), mod->output_buffers); mod->output_buffers = NULL; - rfree(mod->input_buffers); + sof_heap_free(sof_sys_user_heap_get(), mod->input_buffers); mod->input_buffers = NULL; return ret; } @@ -1405,14 +1412,16 @@ int module_adapter_reset(struct comp_dev *dev) if (IS_PROCESSING_MODE_RAW_DATA(mod)) { for (i = 0; i < mod->num_of_sinks; i++) - rfree((__sparse_force void *)mod->output_buffers[i].data); + sof_heap_free(sof_sys_user_heap_get(), + (__sparse_force void *)mod->output_buffers[i].data); for (i = 0; i < mod->num_of_sources; i++) - rfree((__sparse_force void *)mod->input_buffers[i].data); + sof_heap_free(sof_sys_user_heap_get(), + (__sparse_force void *)mod->input_buffers[i].data); } if (IS_PROCESSING_MODE_RAW_DATA(mod) || IS_PROCESSING_MODE_AUDIO_STREAM(mod)) { - rfree(mod->output_buffers); - rfree(mod->input_buffers); + sof_heap_free(sof_sys_user_heap_get(), mod->output_buffers); + sof_heap_free(sof_sys_user_heap_get(), mod->input_buffers); mod->num_of_sources = 0; mod->num_of_sinks = 0; From 77a3ea91c32c436120d2c0be834ed12de17847d6 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 26 Feb 2026 17:39:22 +0200 Subject: [PATCH 063/106] audio: pipeline: use LL scheduler mutex for userspace pipeline triggers In CONFIG_SOF_USERSPACE_LL builds, irq_local_disable() is not available. Replace the no-op irq guard with the LL scheduler's own k_mutex to prevent the scheduler from processing tasks while pipeline state is being updated. The k_mutex is re-entrant so schedule_task() calls inside the critical section are safe. Add zephyr_ll_lock_sched() and zephyr_ll_unlock_sched() helpers that acquire and release the scheduler mutex, and call them from pipeline_schedule_triggered() under CONFIG_SOF_USERSPACE_LL. Signed-off-by: Kai Vehmanen --- src/audio/pipeline/pipeline-schedule.c | 17 ++++++++++++- src/include/sof/schedule/ll_schedule_domain.h | 2 ++ src/schedule/zephyr_ll.c | 24 +++++++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/audio/pipeline/pipeline-schedule.c b/src/audio/pipeline/pipeline-schedule.c index 45fd1eed639c..7e19b43963ff 100644 --- a/src/audio/pipeline/pipeline-schedule.c +++ b/src/audio/pipeline/pipeline-schedule.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -282,6 +283,16 @@ void pipeline_schedule_triggered(struct pipeline_walk_context *ctx, struct pipeline_data *ppl_data = ctx->comp_data; struct list_item *tlist; struct pipeline *p; + +#ifdef CONFIG_SOF_USERSPACE_LL + /* + * In user-space irq_local_disable() is not available. Use the LL + * scheduler mutex to prevent the scheduler from processing tasks + * while pipeline state is being updated. The k_mutex is re-entrant + * so schedule_task() calls inside the critical section are safe. + */ + zephyr_ll_lock_sched(); +#else uint32_t flags; /* @@ -290,6 +301,7 @@ void pipeline_schedule_triggered(struct pipeline_walk_context *ctx, * immediately before all pipelines achieved a consistent state. */ irq_local_disable(flags); +#endif switch (cmd) { case COMP_TRIGGER_PAUSE: @@ -345,8 +357,11 @@ void pipeline_schedule_triggered(struct pipeline_walk_context *ctx, p->xrun_bytes = 1; } } - +#ifdef CONFIG_SOF_USERSPACE_LL + zephyr_ll_unlock_sched(); +#else irq_local_enable(flags); +#endif } int pipeline_comp_ll_task_init(struct pipeline *p) diff --git a/src/include/sof/schedule/ll_schedule_domain.h b/src/include/sof/schedule/ll_schedule_domain.h index 08c4626ef5a6..639dcb5caea6 100644 --- a/src/include/sof/schedule/ll_schedule_domain.h +++ b/src/include/sof/schedule/ll_schedule_domain.h @@ -117,6 +117,8 @@ void zephyr_ll_task_free(struct task *task); struct k_heap *zephyr_ll_user_heap(void); void zephyr_ll_user_resources_init(void); void zephyr_ll_grant_access(struct k_thread *thread); +void zephyr_ll_lock_sched(void); +void zephyr_ll_unlock_sched(void); #endif /* CONFIG_SOF_USERSPACE_LL */ static inline struct ll_schedule_domain *domain_init diff --git a/src/schedule/zephyr_ll.c b/src/schedule/zephyr_ll.c index 801b3671ea67..311e54376ebe 100644 --- a/src/schedule/zephyr_ll.c +++ b/src/schedule/zephyr_ll.c @@ -574,6 +574,30 @@ void zephyr_ll_grant_access(struct k_thread *thread) k_thread_access_grant(thread, ll_sch->lock); } +/** + * Lock the LL scheduler to prevent it from processing tasks. + * + * Uses the LL scheduler's own k_mutex which is re-entrant, so + * schedule_task() calls within the locked section will not deadlock. + * Must be paired with zephyr_ll_unlock_sched(). + */ +void zephyr_ll_lock_sched(void) +{ + struct zephyr_ll *sch = (struct zephyr_ll *)scheduler_get_data(SOF_SCHEDULE_LL_TIMER); + + k_mutex_lock(sch->lock, K_FOREVER); +} + +/** + * Unlock the LL scheduler after a previous zephyr_ll_lock_sched() call. + */ +void zephyr_ll_unlock_sched(void) +{ + struct zephyr_ll *sch = (struct zephyr_ll *)scheduler_get_data(SOF_SCHEDULE_LL_TIMER); + + k_mutex_unlock(sch->lock); +} + #endif /* CONFIG_SOF_USERSPACE_LL */ int zephyr_ll_task_init(struct task *task, From a6fd86dc96fac9a775b867c7826cb435323671a3 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Wed, 22 Apr 2026 15:24:02 +0300 Subject: [PATCH 064/106] schedule: zephyr_ll: replace k_mutex with sys_mutex for scheduler lock - replace dynamically-allocated k_mutex *lock with embedded sys_mutex - remove k_object_alloc and error handling in init - update 4 lock/unlock wrappers to sys_mutex_lock/unlock - remove k_thread_access_grant calls, gut zephyr_ll_grant_access() Signed-off-by: Kai Vehmanen --- src/schedule/zephyr_ll.c | 33 ++++++++++----------------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/src/schedule/zephyr_ll.c b/src/schedule/zephyr_ll.c index 311e54376ebe..2dc5e34c3714 100644 --- a/src/schedule/zephyr_ll.c +++ b/src/schedule/zephyr_ll.c @@ -5,6 +5,7 @@ // Author: Guennadi Liakhovetski #include +#include #include #include #include @@ -32,7 +33,7 @@ struct zephyr_ll { struct ll_schedule_domain *ll_domain; /* scheduling domain */ unsigned int core; /* core ID of this instance */ #if CONFIG_SOF_USERSPACE_LL - struct k_mutex *lock; /* mutex for userspace */ + struct sys_mutex lock; /* mutex for userspace */ #endif struct k_heap *heap; }; @@ -47,7 +48,7 @@ struct zephyr_ll_pdata { static void zephyr_ll_lock(struct zephyr_ll *sch, uint32_t *flags) { #if CONFIG_SOF_USERSPACE_LL - k_mutex_lock(sch->lock, K_FOREVER); + sys_mutex_lock(&sch->lock, K_FOREVER); #else irq_local_disable(*flags); #endif @@ -56,7 +57,7 @@ static void zephyr_ll_lock(struct zephyr_ll *sch, uint32_t *flags) static void zephyr_ll_unlock(struct zephyr_ll *sch, uint32_t *flags) { #if CONFIG_SOF_USERSPACE_LL - k_mutex_unlock(sch->lock); + sys_mutex_unlock(&sch->lock); #else irq_local_enable(*flags); #endif @@ -522,10 +523,6 @@ struct k_thread *zephyr_ll_init_context(void *data, struct task *task) } if (!k_is_user_context()) { - k_thread_access_grant(zephyr_domain_thread_tid(sch->ll_domain), sch->lock); - - tr_dbg(&ll_tr, "granting access to lock %p for thread %p", sch->lock, - zephyr_domain_thread_tid(sch->ll_domain)); tr_dbg(&ll_tr, "granting access to domain lock %p for thread %p", &sch->ll_domain->lock, zephyr_domain_thread_tid(sch->ll_domain)); } @@ -569,15 +566,13 @@ void zephyr_ll_task_free(struct task *task) void zephyr_ll_grant_access(struct k_thread *thread) { - struct zephyr_ll *ll_sch = (struct zephyr_ll *)scheduler_get_data(SOF_SCHEDULE_LL_TIMER); - - k_thread_access_grant(thread, ll_sch->lock); + /* sys_mutex does not require access grants */ } /** * Lock the LL scheduler to prevent it from processing tasks. * - * Uses the LL scheduler's own k_mutex which is re-entrant, so + * Uses the LL scheduler's own sys_mutex which is re-entrant, so * schedule_task() calls within the locked section will not deadlock. * Must be paired with zephyr_ll_unlock_sched(). */ @@ -585,7 +580,7 @@ void zephyr_ll_lock_sched(void) { struct zephyr_ll *sch = (struct zephyr_ll *)scheduler_get_data(SOF_SCHEDULE_LL_TIMER); - k_mutex_lock(sch->lock, K_FOREVER); + sys_mutex_lock(&sch->lock, K_FOREVER); } /** @@ -595,7 +590,7 @@ void zephyr_ll_unlock_sched(void) { struct zephyr_ll *sch = (struct zephyr_ll *)scheduler_get_data(SOF_SCHEDULE_LL_TIMER); - k_mutex_unlock(sch->lock); + sys_mutex_unlock(&sch->lock); } #endif /* CONFIG_SOF_USERSPACE_LL */ @@ -671,16 +666,8 @@ int zephyr_ll_scheduler_init(struct ll_schedule_domain *domain) sch->heap = heap; #if CONFIG_SOF_USERSPACE_LL - /* Allocate mutex dynamically for userspace access */ - sch->lock = k_object_alloc(K_OBJ_MUTEX); - if (!sch->lock) { - tr_err(&ll_tr, "mutex allocation failed"); - sof_heap_free(sch->heap, sch); - return -ENOMEM; - } - k_mutex_init(sch->lock); - - tr_dbg(&ll_tr, "ll-scheduler init done, sch %p sch->lock %p", sch, sch->lock); + sys_mutex_init(&sch->lock); + tr_dbg(&ll_tr, "ll-scheduler init done, sch %p", sch); #endif scheduler_init(domain->type, &zephyr_ll_ops, sch); From 316ffcd32eaecb7057a75a228a823d305da71661 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Tue, 17 Feb 2026 18:40:27 +0200 Subject: [PATCH 065/106] audio: buffer: move dp_heap_user lifecycle to IPC and module adapter The mod_heap_user (dp_heap_user) reference count management in comp_buffer_free() does not belong there - a buffer's free method should not be responsible for freeing a module's private heap. Additionally, in CONFIG_SOF_USERSPACE_LL builds, comp_buffer_free() may run in user-space context where rfree() of kernel-allocated memory is not ok. - Add dp_heap_put() helper to dp_schedule.h for reference-counted DP heap release - Move buffer-side client_count decrements to IPC layer (ipc_comp_disconnect, ipc_pipeline_module_free, ipc_comp_connect error paths) - Move raw data buffer client_count management to module_adapter_free/prepare with DP domain guard - Fix module_adapter_mem_free() where CONFIG_SOF_USERSPACE_LL incorrectly reassigned mod_heap for DP modules - Fix client_count increment guard in ipc_comp_connect() to check dp pointer rather than dp_heap Signed-off-by: Kai Vehmanen --- src/audio/buffers/comp_buffer.c | 6 --- src/audio/module_adapter/module_adapter.c | 24 +++++------ src/include/sof/schedule/dp_schedule.h | 17 ++++++++ src/ipc/ipc4/helper.c | 52 +++++++++++++++++++++-- 4 files changed, 76 insertions(+), 23 deletions(-) diff --git a/src/audio/buffers/comp_buffer.c b/src/audio/buffers/comp_buffer.c index 26c3713e30cb..77471194dfa6 100644 --- a/src/audio/buffers/comp_buffer.c +++ b/src/audio/buffers/comp_buffer.c @@ -157,12 +157,6 @@ static void comp_buffer_free(struct sof_audio_buffer *audio_buffer) sof_heap_free(heap, buffer->stream.addr); sof_heap_free(heap, buffer); - if (heap) { - struct dp_heap_user *mod_heap_user = container_of(heap, struct dp_heap_user, heap); - - if (!--mod_heap_user->client_count) - rfree(mod_heap_user); - } } APP_TASK_DATA static const struct source_ops comp_buffer_source_ops = { diff --git a/src/audio/module_adapter/module_adapter.c b/src/audio/module_adapter/module_adapter.c index 860e518bb6b3..f51a011d0c56 100644 --- a/src/audio/module_adapter/module_adapter.c +++ b/src/audio/module_adapter/module_adapter.c @@ -177,20 +177,9 @@ static void module_adapter_mem_free(struct processing_module *mod) sof_heap_free(mod_heap, mod->priv.cfg.input_pins); #endif sof_heap_free(mod_heap, mod->dev); - LOG_INF("mod"); -#ifdef CONFIG_SOF_USERSPACE_LL - mod_heap = zephyr_ll_user_heap(); - comp_cl_dbg(drv, "using ll user heap for module free"); -#endif - comp_cl_info(drv, "free mod %p with heap %p", mod, mod_heap); sof_heap_free(mod_heap, mod); - if (domain == COMP_PROCESSING_DOMAIN_DP) { - struct dp_heap_user *mod_heap_user = container_of(mod_heap, struct dp_heap_user, - heap); - - if (mod_heap && !--mod_heap_user->client_count) - rfree(mod_heap_user); - } + if (domain == COMP_PROCESSING_DOMAIN_DP && mod_heap) + dp_heap_put(mod_heap); } /* @@ -641,7 +630,8 @@ int module_adapter_prepare(struct comp_dev *dev) goto free; } - if (md->resources.heap && md->resources.heap != dev->drv->user_heap) { + if (dev->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_DP && + md->resources.heap) { struct dp_heap_user *dp_user = container_of(md->resources.heap, struct dp_heap_user, heap); @@ -687,6 +677,9 @@ int module_adapter_prepare(struct comp_dev *dev) list_item_del(&buffer->buffers_list); irq_local_enable(flags); buffer_free(buffer); + if (dev->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_DP && + md->resources.heap) + dp_heap_put(md->resources.heap); } out_data_free: @@ -1479,6 +1472,9 @@ void module_adapter_free(struct comp_dev *dev) list_item_del(&buffer->buffers_list); irq_local_enable(flags); buffer_free(buffer); + if (dev->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_DP && + mod->priv.resources.heap) + dp_heap_put(mod->priv.resources.heap); } mod_free(mod, mod->stream_params); diff --git a/src/include/sof/schedule/dp_schedule.h b/src/include/sof/schedule/dp_schedule.h index 37b8f1fc3f2c..9a63d503329b 100644 --- a/src/include/sof/schedule/dp_schedule.h +++ b/src/include/sof/schedule/dp_schedule.h @@ -8,6 +8,7 @@ #ifndef __SOF_SCHEDULE_DP_SCHEDULE_H__ #define __SOF_SCHEDULE_DP_SCHEDULE_H__ +#include #include #include #include @@ -125,6 +126,22 @@ struct dp_heap_user { unsigned int client_count; /* devices and buffers */ }; +/** + * dp_heap_put() - Release a reference to a DP module heap. + * @heap: The k_heap pointer belonging to a dp_heap_user. + * + * Decrements client_count and frees the dp_heap_user when it reaches zero. + * Must only be called for heaps that are part of a dp_heap_user, i.e. heaps + * allocated by module_adapter_dp_heap_new() for DP domain modules. + */ +static inline void dp_heap_put(struct k_heap *heap) +{ + struct dp_heap_user *mod_heap_user = container_of(heap, struct dp_heap_user, heap); + + if (!--mod_heap_user->client_count) + rfree(mod_heap_user); +} + #if CONFIG_ZEPHYR_DP_SCHEDULER int scheduler_dp_thread_ipc(struct processing_module *pmod, unsigned int cmd, const union scheduler_dp_thread_ipc_param *param); diff --git a/src/ipc/ipc4/helper.c b/src/ipc/ipc4/helper.c index 3404906b9771..7bfe8f3532bf 100644 --- a/src/ipc/ipc4/helper.c +++ b/src/ipc/ipc4/helper.c @@ -457,22 +457,51 @@ __cold static int ipc_pipeline_module_free(uint32_t pipeline_id) /* free sink buffer allocated by current component in bind function */ comp_dev_for_each_consumer_safe(icd->cd, buffer, safe) { +#if CONFIG_ZEPHYR_DP_SCHEDULER + struct k_heap *buf_heap = buffer->audio_buffer.heap; + struct comp_dev *orig_sink = comp_buffer_get_sink_component(buffer); + bool buf_is_dp = buf_heap && + (icd->cd->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_DP || + (orig_sink && orig_sink->ipc_config.proc_domain == + COMP_PROCESSING_DOMAIN_DP)); +#endif + pipeline_disconnect(icd->cd, buffer, PPL_CONN_DIR_COMP_TO_BUFFER); struct comp_dev *sink = comp_buffer_get_sink_component(buffer); /* free the buffer only when the sink module has also been disconnected */ - if (!sink) + if (!sink) { buffer_free(buffer); +#if CONFIG_ZEPHYR_DP_SCHEDULER + if (buf_is_dp) + dp_heap_put(buf_heap); +#endif + } } /* free source buffer allocated by current component in bind function */ comp_dev_for_each_producer_safe(icd->cd, buffer, safe) { +#if CONFIG_ZEPHYR_DP_SCHEDULER + struct k_heap *buf_heap = buffer->audio_buffer.heap; + struct comp_dev *orig_source = + comp_buffer_get_source_component(buffer); + bool buf_is_dp = buf_heap && + (icd->cd->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_DP || + (orig_source && orig_source->ipc_config.proc_domain == + COMP_PROCESSING_DOMAIN_DP)); +#endif + pipeline_disconnect(icd->cd, buffer, PPL_CONN_DIR_BUFFER_TO_COMP); struct comp_dev *source = comp_buffer_get_source_component(buffer); /* free the buffer only when the source module has also been disconnected */ - if (!source) + if (!source) { buffer_free(buffer); +#if CONFIG_ZEPHYR_DP_SCHEDULER + if (buf_is_dp) + dp_heap_put(buf_heap); +#endif + } } if (!cpu_is_me(icd->core)) @@ -738,7 +767,7 @@ __cold int ipc_comp_connect(struct ipc *ipc, ipc_pipe_comp_connect *_connect) } #if CONFIG_ZEPHYR_DP_SCHEDULER - if (dp_heap) { + if (dp) { struct dp_heap_user *dp_user = container_of(dp_heap, struct dp_heap_user, heap); dp_user->client_count++; @@ -782,6 +811,8 @@ __cold int ipc_comp_connect(struct ipc *ipc, ipc_pipe_comp_connect *_connect) buf_get_id(buffer)); if (!ring_buffer) { buffer_free(buffer); + if (dp) + dp_heap_put(dp_heap); return IPC4_OUT_OF_MEMORY; } @@ -869,6 +900,10 @@ __cold int ipc_comp_connect(struct ipc *ipc, ipc_pipe_comp_connect *_connect) free: ll_unblock(cross_core_bind, flags); buffer_free(buffer); +#if CONFIG_ZEPHYR_DP_SCHEDULER + if (dp) + dp_heap_put(dp_heap); +#endif return IPC4_INVALID_RESOURCE_STATE; } @@ -950,6 +985,13 @@ __cold int ipc_comp_disconnect(struct ipc *ipc, ipc_pipe_comp_connect *_connect) #endif } +#if CONFIG_ZEPHYR_DP_SCHEDULER + struct k_heap *buf_heap = buffer->audio_buffer.heap; + bool buf_is_dp = buf_heap && + (src->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_DP || + sink->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_DP); +#endif + pipeline_disconnect(src, buffer, PPL_CONN_DIR_COMP_TO_BUFFER); pipeline_disconnect(sink, buffer, PPL_CONN_DIR_BUFFER_TO_COMP); /* these might call comp_ipc4_bind_remote() if necessary */ @@ -965,6 +1007,10 @@ __cold int ipc_comp_disconnect(struct ipc *ipc, ipc_pipe_comp_connect *_connect) ll_unblock(cross_core_unbind, flags); buffer_free(buffer); +#if CONFIG_ZEPHYR_DP_SCHEDULER + if (buf_is_dp) + dp_heap_put(buf_heap); +#endif if (ret || ret1) return IPC4_INVALID_RESOURCE_ID; From 4cb0b12fcfec3ca55d896565ca63adc4ebb0fd6a Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Tue, 31 Mar 2026 14:40:17 +0300 Subject: [PATCH 066/106] audio: host-zepher: add HOST_DMA_IPC_POSITION_UPDATES Kconfig Add a built option HOST_DMA_IPC_POSITION_UPDATES to control whether functionality to send IPC stream position updates is enabled or not. Most platforms provide more efficient means for host to monitor DMA state, so this code is in most cases unncessary. The current IPC sending code (from audio context) also assume kernel context, so making this functionality user-space compatible will require extra work. Signed-off-by: Kai Vehmanen --- src/audio/Kconfig | 8 ++++++++ src/audio/copier/host_copier.h | 2 ++ src/audio/host-zephyr.c | 10 ++++++++++ src/ipc/ipc-helper.c | 2 ++ 4 files changed, 22 insertions(+) diff --git a/src/audio/Kconfig b/src/audio/Kconfig index 1f7d362ffdc2..3bb4ec58f6bb 100644 --- a/src/audio/Kconfig +++ b/src/audio/Kconfig @@ -42,6 +42,14 @@ config HOST_DMA_STREAM_SYNCHRONIZATION for each group, different than the default one determined by the system tick frequency. This feature will allow host lower power consumption in scenarios with deep buffering. +config HOST_DMA_IPC_POSITION_UPDATES + bool "Support for stream position updates via IPC messages" + default y if IPC_MAJOR_3 + help + Support firmware functionality to report stream position updates + by sending a IPC message whenever one period of audio is transfferred. + Most platforms provide more efficient ways to query the DMA status. + config COMP_CHAIN_DMA bool "Chain DMA component" depends on IPC_MAJOR_4 diff --git a/src/audio/copier/host_copier.h b/src/audio/copier/host_copier.h index ad16c7ac13de..1f148a368de8 100644 --- a/src/audio/copier/host_copier.h +++ b/src/audio/copier/host_copier.h @@ -106,7 +106,9 @@ struct host_data { /* stream info */ struct sof_ipc_stream_posn posn; /* TODO: update this */ +#if CONFIG_HOST_DMA_IPC_POSITION_UPDATES struct ipc_msg *msg; /**< host notification */ +#endif #if CONFIG_XRUN_NOTIFICATIONS_ENABLE bool xrun_notification_sent; #endif diff --git a/src/audio/host-zephyr.c b/src/audio/host-zephyr.c index f59aa38e3f58..0ec2da19e364 100644 --- a/src/audio/host-zephyr.c +++ b/src/audio/host-zephyr.c @@ -246,7 +246,9 @@ void host_common_update(struct host_data *hd, struct comp_dev *dev, uint32_t byt struct comp_buffer *sink; int ret; bool update_mailbox = false; +#if CONFIG_HOST_DMA_IPC_POSITION_UPDATES bool send_ipc = false; +#endif if (dev->direction == SOF_IPC_STREAM_PLAYBACK) { source = hd->dma_buffer; @@ -285,6 +287,7 @@ void host_common_update(struct host_data *hd, struct comp_dev *dev, uint32_t byt if (hd->cont_update_posn) update_mailbox = true; +#if CONFIG_HOST_DMA_IPC_POSITION_UPDATES /* Don't send stream position if no_stream_position == 1 */ if (!hd->no_stream_position) { hd->report_pos += bytes; @@ -304,13 +307,16 @@ void host_common_update(struct host_data *hd, struct comp_dev *dev, uint32_t byt send_ipc = true; } } +#endif if (update_mailbox) { pipeline_get_timestamp(dev->pipeline, dev, &hd->posn); mailbox_stream_write(dev->pipeline->posn_offset, &hd->posn, sizeof(hd->posn)); +#if CONFIG_HOST_DMA_IPC_POSITION_UPDATES if (send_ipc) ipc_msg_send(hd->msg, &hd->posn, false); +#endif } } @@ -721,12 +727,14 @@ __cold int host_common_new(struct host_data *hd, struct comp_dev *dev, ipc_build_stream_posn(&hd->posn, SOF_IPC_STREAM_POSITION, config_id); +#if CONFIG_HOST_DMA_IPC_POSITION_UPDATES hd->msg = ipc_msg_init(hd->posn.rhdr.hdr.cmd, sizeof(hd->posn)); if (!hd->msg) { comp_err(dev, "ipc_msg_init failed"); sof_dma_put(hd->dma); return -ENOMEM; } +#endif hd->chan_index = -1; hd->copy_type = COMP_COPY_NORMAL; @@ -804,7 +812,9 @@ __cold void host_common_free(struct host_data *hd) sof_dma_put(hd->dma); +#if CONFIG_HOST_DMA_IPC_POSITION_UPDATES ipc_msg_free(hd->msg); +#endif dma_sg_free(hd->heap, &hd->config.elem_array); } diff --git a/src/ipc/ipc-helper.c b/src/ipc/ipc-helper.c index cc0f7f929bb9..102f29276cc1 100644 --- a/src/ipc/ipc-helper.c +++ b/src/ipc/ipc-helper.c @@ -291,7 +291,9 @@ __cold int ipc_comp_free(struct ipc *ipc, uint32_t comp_id) struct ipc_comp_dev *icd; struct comp_buffer *buffer; struct comp_buffer *safe; +#ifndef CONFIG_SOF_USERSPACE_LL uint32_t flags; +#endif assert_can_be_cold(); From 526e4dff70796b1ac986c65d585dc3b834fd3c00 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Tue, 31 Mar 2026 14:29:13 +0300 Subject: [PATCH 067/106] audio: copier_host: make FPI sync group availble to user-space The FPI sync functionality relies on static global objects. Make these available to the SOF application even when it is run in user-space. Signed-off-by: Kai Vehmanen --- src/audio/copier/copier_host.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/audio/copier/copier_host.c b/src/audio/copier/copier_host.c index fe17a49328b9..bd5d58933790 100644 --- a/src/audio/copier/copier_host.c +++ b/src/audio/copier/copier_host.c @@ -12,6 +12,10 @@ #include "copier.h" #include "host_copier.h" +#ifdef CONFIG_SOF_USERSPACE_LL +#include +#endif + LOG_MODULE_DECLARE(copier, CONFIG_SOF_LOG_LEVEL); #if CONFIG_HOST_DMA_STREAM_SYNCHRONIZATION @@ -27,7 +31,7 @@ struct fpi_sync_group { struct list_item item; }; -static struct list_item group_list_head = LIST_INIT(group_list_head); +static APP_SYSUSER_BSS struct list_item group_list_head = LIST_INIT(group_list_head); __cold static struct fpi_sync_group *find_group_by_id(uint32_t id) { From 766d92c457d7b6ef76eb36cb6ad27aaf08ec6675 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Tue, 31 Mar 2026 14:31:24 +0300 Subject: [PATCH 068/106] audio: copier: avoid IRQ lock/unlock in chmap code Copier set_chmap() blocks IRQs to atomically update the converters. This code is not safe to be moved to user-space, so replace the locks with calls to block LL scheduler execution. Signed-off-by: Kai Vehmanen --- src/audio/copier/copier.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/audio/copier/copier.c b/src/audio/copier/copier.c index 22063842786d..a427bea3c693 100644 --- a/src/audio/copier/copier.c +++ b/src/audio/copier/copier.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -823,7 +824,9 @@ __cold static int set_chmap(struct comp_dev *dev, const void *data, size_t data_ pcm_converter_func process; pcm_converter_func converters[IPC4_COPIER_MODULE_OUTPUT_PINS_COUNT]; int i; +#ifndef CONFIG_SOF_USERSPACE_LL uint32_t irq_flags; +#endif assert_can_be_cold(); @@ -877,15 +880,26 @@ __cold static int set_chmap(struct comp_dev *dev, const void *data, size_t data_ } } - /* Atomically update chmap, process and converters */ + /* Atomically update chmap, process and converters. + * In user-space builds irq_local_disable() is privileged, + * use the LL scheduler lock instead. + */ +#ifdef CONFIG_SOF_USERSPACE_LL + zephyr_ll_lock_sched(cpu_get_id()); +#else irq_local_disable(irq_flags); +#endif cd->dd[0]->chmap = chmap_cfg->channel_map; cd->dd[0]->process = process; for (i = 0; i < IPC4_COPIER_MODULE_OUTPUT_PINS_COUNT; i++) cd->converter[i] = converters[i]; +#ifdef CONFIG_SOF_USERSPACE_LL + zephyr_ll_unlock_sched(cpu_get_id()); +#else irq_local_enable(irq_flags); +#endif return 0; } From 95af5d7882d36bbcd12321326576a8ec5762febd Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Tue, 31 Mar 2026 14:34:35 +0300 Subject: [PATCH 069/106] audio: module_adapter: avoid IRQ lock/unlock in prepare() The module_adapter prepare() blocks IRQs to atomically to connect the sink/source buffers. This code is not safe to be moved to user-space, so replace the locks with calls to block LL scheduler execution, when compiled for user-space. Signed-off-by: Kai Vehmanen --- src/audio/module_adapter/module_adapter.c | 30 +++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/audio/module_adapter/module_adapter.c b/src/audio/module_adapter/module_adapter.c index f51a011d0c56..5a121c88a6b7 100644 --- a/src/audio/module_adapter/module_adapter.c +++ b/src/audio/module_adapter/module_adapter.c @@ -622,7 +622,9 @@ int module_adapter_prepare(struct comp_dev *dev) memory_flags, PLATFORM_DCACHE_ALIGN, BUFFER_USAGE_NOT_SHARED); +#ifndef CONFIG_SOF_USERSPACE_LL uint32_t flags; +#endif if (!buffer) { comp_err(dev, "failed to allocate local buffer"); @@ -639,9 +641,17 @@ int module_adapter_prepare(struct comp_dev *dev) dp_user->client_count++; } +#ifdef CONFIG_SOF_USERSPACE_LL + zephyr_ll_lock_sched(cpu_get_id()); +#else irq_local_disable(flags); +#endif list_item_prepend(&buffer->buffers_list, &mod->raw_data_buffers_list); +#ifdef CONFIG_SOF_USERSPACE_LL + zephyr_ll_unlock_sched(cpu_get_id()); +#else irq_local_enable(flags); +#endif buffer_set_params(buffer, mod->stream_params, BUFFER_UPDATE_FORCE); audio_buffer_reset(&buffer->audio_buffer); @@ -671,11 +681,21 @@ int module_adapter_prepare(struct comp_dev *dev) list_for_item_safe(blist, _blist, &mod->raw_data_buffers_list) { struct comp_buffer *buffer = container_of(blist, struct comp_buffer, buffers_list); +#ifndef CONFIG_SOF_USERSPACE_LL uint32_t flags; +#endif +#ifdef CONFIG_SOF_USERSPACE_LL + zephyr_ll_lock_sched(cpu_get_id()); +#else irq_local_disable(flags); +#endif list_item_del(&buffer->buffers_list); +#ifdef CONFIG_SOF_USERSPACE_LL + zephyr_ll_unlock_sched(cpu_get_id()); +#else irq_local_enable(flags); +#endif buffer_free(buffer); if (dev->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_DP && md->resources.heap) @@ -1466,11 +1486,21 @@ void module_adapter_free(struct comp_dev *dev) list_for_item_safe(blist, _blist, &mod->raw_data_buffers_list) { struct comp_buffer *buffer = container_of(blist, struct comp_buffer, buffers_list); +#ifndef CONFIG_SOF_USERSPACE_LL uint32_t flags; +#endif +#ifdef CONFIG_SOF_USERSPACE_LL + zephyr_ll_lock_sched(cpu_get_id()); +#else irq_local_disable(flags); +#endif list_item_del(&buffer->buffers_list); +#ifdef CONFIG_SOF_USERSPACE_LL + zephyr_ll_unlock_sched(cpu_get_id()); +#else irq_local_enable(flags); +#endif buffer_free(buffer); if (dev->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_DP && mod->priv.resources.heap) From a57d2b59c822cf497f4753294b7a22c86467ee73 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Tue, 14 Apr 2026 20:26:28 +0300 Subject: [PATCH 070/106] audio: module_adapter: make data_blob compatible with user-space Use user-space friendy sof_heap_alloc() for dynamic allocations in data_blob handler. Signed-off-by: Kai Vehmanen --- src/audio/data_blob.c | 47 +++++++++++++++++++---- src/audio/module_adapter/module/generic.c | 4 +- src/include/sof/audio/data_blob.h | 7 +++- 3 files changed, 46 insertions(+), 12 deletions(-) diff --git a/src/audio/data_blob.c b/src/audio/data_blob.c index 399244106f95..088d54ee89f5 100644 --- a/src/audio/data_blob.c +++ b/src/audio/data_blob.c @@ -28,8 +28,7 @@ struct comp_data_blob_handler { */ uint32_t single_blob:1; /**< Allocate only one blob. Module can not * be active while reconfguring. - */ - void *(*alloc)(size_t size); /**< alternate allocator, maybe null */ + */ struct k_heap *heap; /**< heap for user-safe alloc, or NULL */ void *(*alloc)(size_t size); /**< alternate allocator, maybe null */ void (*free)(void *buf); /**< alternate free(), maybe null */ /** validator for new data, maybe null */ @@ -632,23 +631,52 @@ static void default_free(void *buf) rfree(buf); } +static void *default_heap_alloc(size_t size) +{ + return sof_heap_alloc(sof_sys_user_heap_get(), + SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, + size, 0); +} + +static void default_heap_free(void *buf) +{ + sof_heap_free(sof_sys_user_heap_get(), buf); +} + struct comp_data_blob_handler * comp_data_blob_handler_new_ext(struct comp_dev *dev, bool single_blob, void *(*alloc)(size_t size), - void (*free)(void *buf)) + void (*free)(void *buf), + struct k_heap *heap) { struct comp_data_blob_handler *handler; comp_dbg(dev, "entry"); - handler = rzalloc(SOF_MEM_FLAG_USER, - sizeof(struct comp_data_blob_handler)); + if (heap) + handler = sof_heap_alloc(heap, + SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, + sizeof(struct comp_data_blob_handler), 0); + else + handler = rzalloc(SOF_MEM_FLAG_USER, + sizeof(struct comp_data_blob_handler)); if (handler) { + if (heap) + memset(handler, 0, sizeof(*handler)); handler->dev = dev; handler->single_blob = single_blob; - handler->alloc = alloc ? alloc : default_alloc; - handler->free = free ? free : default_free; + handler->heap = heap; + if (alloc) { + handler->alloc = alloc; + handler->free = free ? free : default_free; + } else if (heap) { + handler->alloc = default_heap_alloc; + handler->free = default_heap_free; + } else { + handler->alloc = default_alloc; + handler->free = free ? free : default_free; + } } return handler; @@ -662,6 +690,9 @@ void comp_data_blob_handler_free(struct comp_data_blob_handler *blob_handler) comp_free_data_blob(blob_handler); - rfree(blob_handler); + if (blob_handler->heap) + sof_heap_free(blob_handler->heap, blob_handler); + else + rfree(blob_handler); } EXPORT_SYMBOL(comp_data_blob_handler_free); diff --git a/src/audio/module_adapter/module/generic.c b/src/audio/module_adapter/module/generic.c index 179d412b5a99..ad6a64fc20db 100644 --- a/src/audio/module_adapter/module/generic.c +++ b/src/audio/module_adapter/module/generic.c @@ -277,7 +277,7 @@ EXPORT_SYMBOL(z_impl_mod_alloc_ext); #if CONFIG_COMP_BLOB struct comp_data_blob_handler *mod_data_blob_handler_new(struct processing_module *mod) { - struct module_resources * __maybe_unused res = &mod->priv.resources; + struct module_resources *res = &mod->priv.resources; struct comp_data_blob_handler *bhp; struct module_resource *container; @@ -287,7 +287,7 @@ struct comp_data_blob_handler *mod_data_blob_handler_new(struct processing_modul if (!container) return NULL; - bhp = comp_data_blob_handler_new_ext(mod->dev, false, NULL, NULL); + bhp = comp_data_blob_handler_new_ext(mod->dev, false, NULL, NULL, res->heap); if (!bhp) { container_put(mod, container); return NULL; diff --git a/src/include/sof/audio/data_blob.h b/src/include/sof/audio/data_blob.h index d2eb8a74d66d..58505b2e199b 100644 --- a/src/include/sof/audio/data_blob.h +++ b/src/include/sof/audio/data_blob.h @@ -12,6 +12,7 @@ #include struct comp_dev; +struct k_heap; struct comp_data_blob_handler; @@ -113,11 +114,13 @@ int comp_data_blob_get_cmd(struct comp_data_blob_handler *blob_handler, * @param single_blob Set true for single configuration blob operation * @param alloc Optional blob memory allocator function pointer * @param free Optional blob memory free function pointer + * @param heap Optional heap for user-safe allocation, or NULL for default */ struct comp_data_blob_handler * comp_data_blob_handler_new_ext(struct comp_dev *dev, bool single_blob, void *(*alloc)(size_t size), - void (*free)(void *buf)); + void (*free)(void *buf), + struct k_heap *heap); /** * Returns new data blob handler. @@ -130,7 +133,7 @@ comp_data_blob_handler_new_ext(struct comp_dev *dev, bool single_blob, static inline struct comp_data_blob_handler *comp_data_blob_handler_new(struct comp_dev *dev) { - return comp_data_blob_handler_new_ext(dev, false, NULL, NULL); + return comp_data_blob_handler_new_ext(dev, false, NULL, NULL, NULL); } /** From b6811eb21ae13c9c161277caef2d667909e36ff7 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 16 Apr 2026 14:28:54 +0300 Subject: [PATCH 071/106] audio: module-adapter: make generic.c user-space compatible Replace direct rballoc() and rfree() calls with sys_alloc_heap() and sys_alloc_free(), and use sof_sys_user_heap_get() as the heap. This makes the code ready to be used in user-space, including module prepare and free stages. Signed-off-by: Kai Vehmanen --- src/audio/module_adapter/module/generic.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/audio/module_adapter/module/generic.c b/src/audio/module_adapter/module/generic.c index ad6a64fc20db..2b5dd04fc1aa 100644 --- a/src/audio/module_adapter/module/generic.c +++ b/src/audio/module_adapter/module/generic.c @@ -56,13 +56,15 @@ int module_load_config(struct comp_dev *dev, const void *cfg, size_t size) if (!dst->data) { /* No space for config available yet, allocate now */ - dst->data = rballoc(SOF_MEM_FLAG_USER, size); + dst->data = sof_heap_alloc(sof_sys_user_heap_get(), + SOF_MEM_FLAG_USER, size, 0); } else if (dst->size != size) { /* The size allocated for previous config doesn't match the new one. * Free old container and allocate new one. */ - rfree(dst->data); - dst->data = rballoc(SOF_MEM_FLAG_USER, size); + sof_heap_free(sof_sys_user_heap_get(), dst->data); + dst->data = sof_heap_alloc(sof_sys_user_heap_get(), + SOF_MEM_FLAG_USER, size, 0); } if (!dst->data) { comp_err(dev, "failed to allocate space for setup config."); @@ -523,7 +525,7 @@ int module_prepare(struct processing_module *mod, * as it has been applied during the procedure - it is safe to * free it. */ - rfree(md->cfg.data); + sof_heap_free(sof_sys_user_heap_get(), md->cfg.data); md->cfg.avail = false; md->cfg.data = NULL; @@ -658,7 +660,7 @@ int module_reset(struct processing_module *mod) md->cfg.avail = false; md->cfg.size = 0; - rfree(md->cfg.data); + sof_heap_free(sof_sys_user_heap_get(), md->cfg.data); md->cfg.data = NULL; #if CONFIG_IPC_MAJOR_3 @@ -709,10 +711,10 @@ int module_free(struct processing_module *mod) /* Free all memory shared by module_adapter & module */ md->cfg.avail = false; md->cfg.size = 0; - rfree(md->cfg.data); + sof_heap_free(sof_sys_user_heap_get(), md->cfg.data); md->cfg.data = NULL; if (md->runtime_params) { - rfree(md->runtime_params); + sof_heap_free(sof_sys_user_heap_get(), md->runtime_params); md->runtime_params = NULL; } #if CONFIG_IPC_MAJOR_3 @@ -779,7 +781,9 @@ int module_set_configuration(struct processing_module *mod, } /* Allocate buffer for new params */ - md->runtime_params = rballoc(SOF_MEM_FLAG_USER, md->new_cfg_size); + md->runtime_params = sof_heap_alloc(sof_sys_user_heap_get(), + SOF_MEM_FLAG_USER, + md->new_cfg_size, 0); if (!md->runtime_params) { comp_err(dev, "space allocation for new params failed"); return -ENOMEM; @@ -820,7 +824,7 @@ int module_set_configuration(struct processing_module *mod, md->new_cfg_size = 0; if (md->runtime_params) - rfree(md->runtime_params); + sof_heap_free(sof_sys_user_heap_get(), md->runtime_params); md->runtime_params = NULL; return ret; From fc0505643f314412d886ff03a59992caa0faca90 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Wed, 15 Apr 2026 13:52:05 +0300 Subject: [PATCH 072/106] audio: make comp_drivers_get() accessible from user-space Make comp_drivers_get() usable from user-space when SOF is built with CONFIG_SOF_USERSPACE_LL. Signed-off-by: Kai Vehmanen --- src/audio/component.c | 8 ++++++++ src/audio/data_blob.c | 4 +++- src/include/sof/audio/component_ext.h | 4 ++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/audio/component.c b/src/audio/component.c index dd9a6e32835b..4369dc8af773 100644 --- a/src/audio/component.c +++ b/src/audio/component.c @@ -38,6 +38,14 @@ LOG_MODULE_REGISTER(component, CONFIG_SOF_LOG_LEVEL); static APP_SYSUSER_BSS SHARED_DATA struct comp_driver_list cd; +#ifdef CONFIG_SOF_USERSPACE_LL +struct comp_driver_list *comp_drivers_get(void) +{ + return platform_shared_get(&cd, sizeof(cd)); +} +EXPORT_SYMBOL(comp_drivers_get); +#endif + SOF_DEFINE_REG_UUID(component); DECLARE_TR_CTX(comp_tr, SOF_UUID(component_uuid), LOG_LEVEL_INFO); diff --git a/src/audio/data_blob.c b/src/audio/data_blob.c index 088d54ee89f5..f62be15e2ed8 100644 --- a/src/audio/data_blob.c +++ b/src/audio/data_blob.c @@ -28,7 +28,9 @@ struct comp_data_blob_handler { */ uint32_t single_blob:1; /**< Allocate only one blob. Module can not * be active while reconfguring. - */ struct k_heap *heap; /**< heap for user-safe alloc, or NULL */ void *(*alloc)(size_t size); /**< alternate allocator, maybe null */ + */ + struct k_heap *heap; /**< heap for user-safe alloc, or NULL */ + void *(*alloc)(size_t size); /**< alternate allocator, maybe null */ void (*free)(void *buf); /**< alternate free(), maybe null */ /** validator for new data, maybe null */ diff --git a/src/include/sof/audio/component_ext.h b/src/include/sof/audio/component_ext.h index d2bbf87a7764..6d67b44014c2 100644 --- a/src/include/sof/audio/component_ext.h +++ b/src/include/sof/audio/component_ext.h @@ -425,10 +425,14 @@ static inline void comp_make_shared(struct comp_dev *dev) dev->is_shared = true; } +#ifdef CONFIG_SOF_USERSPACE_LL +struct comp_driver_list *comp_drivers_get(void); +#else static inline struct comp_driver_list *comp_drivers_get(void) { return sof_get()->comp_drivers; } +#endif #if CONFIG_IPC_MAJOR_4 static inline int comp_ipc4_bind_remote(struct comp_dev *dev, struct bind_info *bind_data) From 503899c85780c9845f80be67b7c7ff57556cf4c7 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Wed, 15 Apr 2026 13:55:06 +0300 Subject: [PATCH 073/106] dai: turn dai_get_device() into a syscall Make dai_get_device() a syscall, so it can be called from user-space threads. Signed-off-by: Kai Vehmanen --- src/include/sof/lib/dai-zephyr.h | 6 +++++- src/lib/dai.c | 2 +- zephyr/CMakeLists.txt | 2 ++ zephyr/syscall/dai.c | 14 ++++++++++++++ 4 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 zephyr/syscall/dai.c diff --git a/src/include/sof/lib/dai-zephyr.h b/src/include/sof/lib/dai-zephyr.h index 34d077c92d7a..71b8e5be4c1f 100644 --- a/src/include/sof/lib/dai-zephyr.h +++ b/src/include/sof/lib/dai-zephyr.h @@ -308,7 +308,9 @@ void dai_release_llp_slot(struct dai_data *dd); /** * \brief Retrieve a pointer to the Zephyr device structure for a DAI of a given type and index. */ -const struct device *dai_get_device(enum sof_ipc_dai_type type, uint32_t index); +__syscall const struct device *dai_get_device(enum sof_ipc_dai_type type, uint32_t index); + +const struct device *z_impl_dai_get_device(enum sof_ipc_dai_type type, uint32_t index); /** * \brief Retrieve the list of all DAI devices. @@ -318,4 +320,6 @@ const struct device *dai_get_device(enum sof_ipc_dai_type type, uint32_t index); const struct device **dai_get_device_list(size_t *count); /** @}*/ +#include + #endif /* __SOF_LIB_DAI_ZEPHYR_H__ */ diff --git a/src/lib/dai.c b/src/lib/dai.c index 0af3d99ceb51..d42c99eb1bc2 100644 --- a/src/lib/dai.c +++ b/src/lib/dai.c @@ -237,7 +237,7 @@ static int sof_dai_type_to_zephyr(uint32_t type) } } -const struct device *dai_get_device(enum sof_ipc_dai_type type, uint32_t index) +const struct device *z_impl_dai_get_device(enum sof_ipc_dai_type type, uint32_t index) { struct dai_config cfg; int z_type; diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index f26192bd09a0..cb5bbef0239c 100644 --- a/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -551,6 +551,8 @@ zephyr_syscall_header(${SOF_SRC_PATH}/include/sof/audio/module_adapter/module/ge zephyr_syscall_header(${SOF_SRC_PATH}/include/sof/lib/fast-get.h) zephyr_syscall_header(include/rtos/alloc.h) zephyr_library_sources(syscall/alloc.c) +zephyr_syscall_header(${SOF_SRC_PATH}/include/sof/lib/dai-zephyr.h) +zephyr_library_sources(syscall/dai.c) zephyr_library_link_libraries(SOF) target_link_libraries(SOF INTERFACE zephyr_interface) diff --git a/zephyr/syscall/dai.c b/zephyr/syscall/dai.c new file mode 100644 index 000000000000..ea4f233fa40d --- /dev/null +++ b/zephyr/syscall/dai.c @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 Intel Corporation. + +#include +#include +#include + +static inline const struct device *z_vrfy_dai_get_device(enum sof_ipc_dai_type type, + uint32_t index) +{ + return z_impl_dai_get_device(type, index); +} +#include From c825bd3be459bad2f8ede89a3e848062ba87cc95 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Tue, 21 Apr 2026 16:09:15 +0300 Subject: [PATCH 074/106] audio: chain_dma: add user-space memory and scheduling support Add CONFIG_SOF_USERSPACE_LL support to chain DMA component: - Allocate comp_dev, private data, and DMA buffer from user heap - Use k_work_delayable for periodic task instead of LL timer scheduler to keep DMA operations in kernel context - Guard all changes with #ifdef CONFIG_SOF_USERSPACE_LL Signed-off-by: Kai Vehmanen --- src/audio/chain_dma.c | 91 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/src/audio/chain_dma.c b/src/audio/chain_dma.c index 5bf13fe733a9..ca4c63f26895 100644 --- a/src/audio/chain_dma.c +++ b/src/audio/chain_dma.c @@ -15,7 +15,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -59,6 +61,15 @@ struct chain_dma_data { bool xrun_notification_sent; #endif +#ifdef CONFIG_SOF_USERSPACE_LL + /** Kernel workqueue scheduling for chain DMA in userspace builds. + * Chain DMA needs kernel context for DMA operations, so it cannot + * run on the user-space LL timer thread. + */ + struct k_work_delayable dma_work; + bool stopped; +#endif + /* local host DMA config */ struct sof_dma *dma_host; struct dma_chan_data *chan_host; @@ -268,6 +279,25 @@ static enum task_state chain_task_run(void *data) return SOF_TASK_STATE_RESCHEDULE; } +#ifdef CONFIG_SOF_USERSPACE_LL +/** Kernel workqueue handler for chain DMA periodic task. + * Runs chain_task_run() in kernel context and reschedules if needed. + */ +static void chain_dma_work_handler(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct chain_dma_data *cd = CONTAINER_OF(dwork, struct chain_dma_data, dma_work); + enum task_state state; + + if (cd->stopped) + return; + + state = chain_task_run(cd); + if (state == SOF_TASK_STATE_RESCHEDULE && !cd->stopped) + k_work_reschedule(dwork, K_USEC(LL_TIMER_PERIOD_US)); +} +#endif + static int chain_task_start(struct comp_dev *dev) { struct chain_dma_data *cd = comp_get_drvdata(dev); @@ -309,6 +339,12 @@ static int chain_task_start(struct comp_dev *dev) } } +#ifdef CONFIG_SOF_USERSPACE_LL + cd->stopped = false; + k_work_init_delayable(&cd->dma_work, chain_dma_work_handler); + k_work_reschedule(&cd->dma_work, K_NO_WAIT); + cd->chain_task.state = SOF_TASK_STATE_QUEUED; +#else ret = schedule_task_init_ll(&cd->chain_task, SOF_UUID(chain_dma_uuid), SOF_SCHEDULE_LL_TIMER, SOF_TASK_PRI_HIGH, chain_task_run, cd, 0, 0); @@ -323,16 +359,19 @@ static int chain_task_start(struct comp_dev *dev) schedule_task_free(&cd->chain_task); goto error_task; } +#endif pm_policy_state_lock_get(PM_STATE_RUNTIME_IDLE, PM_ALL_SUBSTATES); return 0; +#ifndef CONFIG_SOF_USERSPACE_LL error_task: chain_host_stop(dev); chain_link_stop(dev); return ret; +#endif } static int chain_task_pause(struct comp_dev *dev) @@ -340,10 +379,18 @@ static int chain_task_pause(struct comp_dev *dev) struct chain_dma_data *cd = comp_get_drvdata(dev); int ret, ret2; +#ifdef CONFIG_SOF_USERSPACE_LL + if (cd->chain_task.state == SOF_TASK_STATE_FREE) + return 0; + + cd->stopped = true; + cd->first_data_received = false; +#else if (cd->chain_task.state == SOF_TASK_STATE_FREE) return 0; cd->first_data_received = false; +#endif if (cd->stream_direction == SOF_IPC_STREAM_PLAYBACK) { ret = chain_host_stop(dev); ret2 = chain_link_stop(dev); @@ -354,7 +401,12 @@ static int chain_task_pause(struct comp_dev *dev) if (!ret) ret = ret2; +#ifdef CONFIG_SOF_USERSPACE_LL + k_work_cancel_delayable_sync(&cd->dma_work, &(struct k_work_sync){}); + cd->chain_task.state = SOF_TASK_STATE_FREE; +#else schedule_task_free(&cd->chain_task); +#endif pm_policy_state_lock_put(PM_STATE_RUNTIME_IDLE, PM_ALL_SUBSTATES); return ret; @@ -579,8 +631,14 @@ __cold static int chain_task_init(struct comp_dev *dev, uint8_t host_dma_id, uin fifo_size = ALIGN_UP_INTERNAL(fifo_size, addr_align); /* allocate not shared buffer */ +#ifdef CONFIG_SOF_USERSPACE_LL + cd->dma_buffer = buffer_alloc(sof_sys_user_heap_get(), fifo_size, + SOF_MEM_FLAG_USER | SOF_MEM_FLAG_DMA, + addr_align, BUFFER_USAGE_NOT_SHARED); +#else cd->dma_buffer = buffer_alloc(NULL, fifo_size, SOF_MEM_FLAG_USER | SOF_MEM_FLAG_DMA, addr_align, BUFFER_USAGE_NOT_SHARED); +#endif if (!cd->dma_buffer) { comp_err(dev, "failed to alloc dma buffer"); @@ -639,14 +697,31 @@ __cold static struct comp_dev *chain_task_create(const struct comp_driver *drv, if (host_dma_id >= max_chain_number) return NULL; +#ifdef CONFIG_SOF_USERSPACE_LL + dev = sof_heap_alloc(sof_sys_user_heap_get(), + SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, + sizeof(*dev), 0); + if (!dev) + return NULL; + + memset(dev, 0, sizeof(*dev)); + comp_init(drv, dev, sizeof(*dev)); +#else dev = comp_alloc(drv, sizeof(*dev)); if (!dev) return NULL; +#endif +#ifdef CONFIG_SOF_USERSPACE_LL + cd = sof_heap_alloc(sof_sys_user_heap_get(), SOF_MEM_FLAG_USER, sizeof(*cd), 0); +#else cd = rzalloc(SOF_MEM_FLAG_USER, sizeof(*cd)); +#endif if (!cd) goto error; + memset(cd, 0, sizeof(*cd)); + cd->first_data_received = false; cd->cs = scs ? 2 : 4; cd->chain_task.state = SOF_TASK_STATE_INIT; @@ -657,9 +732,17 @@ __cold static struct comp_dev *chain_task_create(const struct comp_driver *drv, if (!ret) return dev; +#ifdef CONFIG_SOF_USERSPACE_LL + sof_heap_free(sof_sys_user_heap_get(), cd); +#else rfree(cd); +#endif error: +#ifdef CONFIG_SOF_USERSPACE_LL + sof_heap_free(sof_sys_user_heap_get(), dev); +#else comp_free_device(dev); +#endif return NULL; } @@ -670,8 +753,16 @@ __cold static void chain_task_free(struct comp_dev *dev) assert_can_be_cold(); chain_release(dev); +#ifdef CONFIG_SOF_USERSPACE_LL + sof_heap_free(sof_sys_user_heap_get(), cd); +#else rfree(cd); +#endif +#ifdef CONFIG_SOF_USERSPACE_LL + sof_heap_free(sof_sys_user_heap_get(), dev); +#else comp_free_device(dev); +#endif } static const struct comp_driver comp_chain_dma = { From 6ede5774c42748095ff2121908c75c64ee42b480 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Wed, 25 Mar 2026 19:31:49 +0200 Subject: [PATCH 075/106] (---section audio user PRs STOP) From e0f16888266fb39077be85e4940fe67f08f499b5 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Tue, 17 Mar 2026 20:21:06 +0200 Subject: [PATCH 076/106] (---section: IPC user support START) From 96b722a6ac7b0825e45c5bec674bdb095d624406 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Wed, 1 Apr 2026 14:08:26 +0300 Subject: [PATCH 077/106] userspace: split ipc files into user and kernel features. REVISIT memcpy This is a mostly mechanical split of initial ipc logic into kernel and user files. This is the 1st stage in supporting both privileged kernel and non privileged userspace IPC commands and security surfaces. At a high level library loading and PM will reside as kernel IPC and pipeline and module will become user IPCs. There will be no impact for devices without MMU. Signed-off-by: Liam Girdwood (cherry picked from commit e65c6d14a05c06e048202d7ee02996922265734a) --- src/include/sof/ipc/common.h | 44 ++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/include/sof/ipc/common.h b/src/include/sof/ipc/common.h index e46fc10b9521..4260a9383ab5 100644 --- a/src/include/sof/ipc/common.h +++ b/src/include/sof/ipc/common.h @@ -24,6 +24,7 @@ struct dma_sg_elem_array; struct ipc_msg; +struct ipc4_message_request; /* validates internal non tail structures within IPC command structure */ #define IPC_IS_SIZE_INVALID(object) \ @@ -166,6 +167,49 @@ struct dai_data; */ int ipc_dai_data_config(struct dai_data *dd, struct comp_dev *dev); +/** + * \brief Processes IPC4 userspace module message. + * @param[in] ipc4 IPC4 message request. + * @param[in] reply IPC message reply structure. + * @return IPC4_SUCCESS on success, error code otherwise. + */ +int ipc4_user_process_module_message(struct ipc4_message_request *ipc4, struct ipc_msg *reply); + +/** + * \brief Processes IPC4 userspace global message. + * @param[in] ipc4 IPC4 message request. + * @param[in] reply IPC message reply structure. + * @return IPC4_SUCCESS on success, error code otherwise. + */ +int ipc4_user_process_glb_message(struct ipc4_message_request *ipc4, struct ipc_msg *reply); + +/** + * \brief Increment the IPC compound message pre-start counter. + * @param[in] msg_id IPC message ID. + */ +void ipc_compound_pre_start(int msg_id); + +/** + * \brief Decrement the IPC compound message pre-start counter on return value status. + * @param[in] msg_id IPC message ID. + * @param[in] ret Return value of the IPC command. + * @param[in] delayed True if the reply is delayed. + */ +void ipc_compound_post_start(uint32_t msg_id, int ret, bool delayed); + +/** + * \brief Complete the IPC compound message. + * @param[in] msg_id IPC message ID. + * @param[in] error Error code of the IPC command. + */ +void ipc_compound_msg_done(uint32_t msg_id, int error); + +/** + * \brief Wait for the IPC compound message to complete. + * @return 0 on success, error code otherwise on timeout. + */ +int ipc_wait_for_compound_msg(void); + /** * \brief create a IPC boot complete message. * @param[in] header header. From e717d81f3564bfcf8904d275741b39496add1c42 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 26 Mar 2026 15:32:33 +0200 Subject: [PATCH 078/106] ipc4: helper: use LL scheduler lock for userspace builds The ll_block()/ll_unblock() macros use irq_local_disable() and irq_local_enable() for the same-core binding path, or when CONFIG_CROSS_CORE_STREAM is not enabled. These are not available in user-space. Use zephyr_ll_lock_sched()/zephyr_ll_unlock_sched() when building with CONFIG_SOF_USERSPACE_LL to acquire the LL scheduler's own k_mutex instead. Signed-off-by: Kai Vehmanen --- src/ipc/ipc4/helper.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/ipc/ipc4/helper.c b/src/ipc/ipc4/helper.c index 7bfe8f3532bf..106f4fa4257b 100644 --- a/src/ipc/ipc4/helper.c +++ b/src/ipc/ipc4/helper.c @@ -588,6 +588,23 @@ __cold static struct comp_buffer *ipc4_create_buffer(struct comp_dev *src, bool * disable any interrupts. */ +#if CONFIG_SOF_USERSPACE_LL +#define ll_block(cross_core_bind, flags) \ + do { \ + if (cross_core_bind) \ + domain_block(sof_get()->platform_timer_domain); \ + else \ + zephyr_ll_lock_sched(); \ + } while (0) + +#define ll_unblock(cross_core_bind, flags) \ + do { \ + if (cross_core_bind) \ + domain_unblock(sof_get()->platform_timer_domain); \ + else \ + zephyr_ll_unlock_sched(); \ + } while (0) +#else #define ll_block(cross_core_bind, flags) \ do { \ if (cross_core_bind) \ @@ -603,6 +620,7 @@ __cold static struct comp_buffer *ipc4_create_buffer(struct comp_dev *src, bool else \ irq_local_enable(flags); \ } while (0) +#endif /* CONFIG_SOF_USERSPACE_LL */ /* Calling both ll_block() and ll_wait_finished_on_core() makes sure LL will not start its * next cycle and its current cycle on specified core has finished. @@ -634,8 +652,13 @@ static int ll_wait_finished_on_core(struct comp_dev *dev) #else +#if CONFIG_SOF_USERSPACE_LL +#define ll_block(cross_core_bind, flags) zephyr_ll_lock_sched() +#define ll_unblock(cross_core_bind, flags) zephyr_ll_unlock_sched() +#else #define ll_block(cross_core_bind, flags) irq_local_disable(flags) #define ll_unblock(cross_core_bind, flags) irq_local_enable(flags) +#endif /* CONFIG_SOF_USERSPACE_LL */ #endif From 05a0ef69ca82fabde91a1ca49a79194b5f84dfd0 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 12 Mar 2026 16:33:15 +0200 Subject: [PATCH 079/106] WIP: ipc: implement user-space IPC handling for CREATE_PIPELINE Add a dedicated user-space thread for handling IPC commands that operate on audio pipelines when CONFIG_SOF_USERSPACE_LL is enabled. The kernel IPC handler forwards CREATE_PIPELINE messages to the user thread via k_event signaling and collects the result through a k_sem handshake. The IPC task_mask IPC_TASK_IN_THREAD bit prevents host completion until the user thread finishes. Key changes: - add ipc_user struct and user-space IPC thread with event loop - allocate IPC context statically in a dedicated memory partition - replace sof_get()->ipc with ipc_get() returning static context - forward CREATE_PIPELINE to user thread via ipc_user_forward_cmd() - allocate pipeline and IPC container from user-space heap This is a in-progress implementation that only handles one IPC message type and is thus not a full implementation. This does allow to proceed to test IPC user thread creation and the basic mechanism to handle messages. Signed-off-by: Kai Vehmanen --- src/include/sof/ipc/common.h | 36 +++++ src/ipc/ipc-common.c | 249 ++++++++++++++++++++++++++++++++--- src/ipc/ipc4/handler-user.c | 7 + src/ipc/ipc4/helper.c | 25 +++- zephyr/include/rtos/sof.h | 2 + 5 files changed, 297 insertions(+), 22 deletions(-) diff --git a/src/include/sof/ipc/common.h b/src/include/sof/ipc/common.h index 4260a9383ab5..6727175aa4b4 100644 --- a/src/include/sof/ipc/common.h +++ b/src/include/sof/ipc/common.h @@ -54,6 +54,20 @@ extern struct tr_ctx ipc_tr; #define IPC_TASK_SECONDARY_CORE BIT(2) #define IPC_TASK_POWERDOWN BIT(3) +struct ipc_user { + struct k_thread *thread; + struct k_sem *sem; + struct k_event *event; + /** @brief Copy of IPC4 message primary word forwarded to user thread */ + uint32_t ipc_msg_pri; + /** @brief Copy of IPC4 message extension word forwarded to user thread */ + uint32_t ipc_msg_ext; + /** @brief Result code from user thread processing */ + int result; + struct ipc *ipc; + struct k_thread *audio_thread; +}; + struct ipc { struct k_spinlock lock; /* locking mechanism */ void *comp_data; @@ -75,6 +89,10 @@ struct ipc { struct task ipc_task; #endif +#ifdef CONFIG_SOF_USERSPACE_LL + struct ipc_user *ipc_user_pdata; +#endif + #ifdef CONFIG_SOF_TELEMETRY_IO_PERFORMANCE_MEASUREMENTS /* io performance measurement */ struct io_perf_data_item *io_perf_in_msg_count; @@ -96,6 +114,12 @@ struct ipc { extern struct task_ops ipc_task_ops; +#ifdef CONFIG_SOF_USERSPACE_LL + +struct ipc *ipc_get(void); + +#else + /** * \brief Get the IPC global context. * @return The global IPC context. @@ -105,6 +129,8 @@ static inline struct ipc *ipc_get(void) return sof_get()->ipc; } +#endif /* CONFIG_SOF_USERSPACE_LL */ + /** * \brief Initialise global IPC context. * @param[in,out] sof Global SOF context. @@ -294,4 +320,14 @@ void ipc_complete_cmd(struct ipc *ipc); /* GDB stub: should enter GDB after completing the IPC processing */ extern bool ipc_enter_gdb; +#ifdef CONFIG_SOF_USERSPACE_LL +struct ipc4_message_request; +/** + * @brief Forward an IPC4 command to the user-space thread. + * @param ipc4 Pointer to the IPC4 message request + * @return Result from user thread processing + */ +int ipc_user_forward_cmd(struct ipc4_message_request *ipc4); +#endif + #endif /* __SOF_DRIVERS_IPC_H__ */ diff --git a/src/ipc/ipc-common.c b/src/ipc/ipc-common.c index a62223dc97a2..c620f005be22 100644 --- a/src/ipc/ipc-common.c +++ b/src/ipc/ipc-common.c @@ -24,6 +24,8 @@ #include #include #include +#include +#include #include #include #include @@ -35,6 +37,16 @@ #include #include +#ifdef __ZEPHYR__ +#include +#endif + +#ifdef CONFIG_SOF_USERSPACE_LL +#include +#include +#include +#endif + #include LOG_MODULE_REGISTER(ipc, CONFIG_SOF_LOG_LEVEL); @@ -43,6 +55,18 @@ SOF_DEFINE_REG_UUID(ipc); DECLARE_TR_CTX(ipc_tr, SOF_UUID(ipc_uuid), LOG_LEVEL_INFO); +#ifdef CONFIG_SOF_USERSPACE_LL +K_APPMEM_PARTITION_DEFINE(ipc_context_part); + +K_APP_BMEM(ipc_context_part) static struct ipc ipc_context; + +struct ipc *ipc_get(void) +{ + return &ipc_context; +} +EXPORT_SYMBOL(ipc_get); +#endif + int ipc_process_on_core(uint32_t core, bool blocking) { struct ipc *ipc = ipc_get(); @@ -256,7 +280,11 @@ void ipc_msg_send(struct ipc_msg *msg, void *data, bool high_priority) list_item_append(&msg->list, &ipc->msg_list); } +#if 0 /*def CONFIG_SOF_USERSPACE_LL */ + LOG_WRN("Skipping IPC worker schedule. TODO to fix\n"); +#else schedule_ipc_worker(); +#endif k_spin_unlock(&ipc->lock, key); } @@ -288,29 +316,208 @@ void ipc_schedule_process(struct ipc *ipc) #endif } +#ifdef CONFIG_SOF_USERSPACE_LL +/* User-space thread for pipeline_two_components test */ +#define IPC_USER_STACKSIZE 8192 + +#define IPC_USER_EVENT_CMD BIT(0) +#define IPC_USER_EVENT_STOP BIT(1) + +static struct k_thread ipc_user_thread; +static K_THREAD_STACK_DEFINE(ipc_user_stack, IPC_USER_STACKSIZE); + +/** + * @brief Forward an IPC4 command to the user-space thread. + * + * Called from kernel context (IPC EDF task) to forward the IPC4 + * message to the user-space thread for processing. Sets + * IPC_TASK_IN_THREAD in task_mask so the host is not signaled + * until the user thread completes. Blocks until the user thread + * finishes processing and returns the result. + * + * @param ipc4 Pointer to the IPC4 message request + * @return Result from user thread processing + */ +int ipc_user_forward_cmd(struct ipc4_message_request *ipc4) +{ + struct ipc *ipc = ipc_get(); + struct ipc_user *pdata = ipc->ipc_user_pdata; + k_spinlock_key_t key; + int ret; + + LOG_DBG("IPC: forward cmd %08x", ipc4->primary.dat); + + /* Copy message words — original buffer may be reused */ + pdata->ipc_msg_pri = ipc4->primary.dat; + pdata->ipc_msg_ext = ipc4->extension.dat; + pdata->ipc = ipc; + + /* Prevent host completion until user thread finishes */ + key = k_spin_lock(&ipc->lock); + ipc->task_mask |= IPC_TASK_IN_THREAD; + k_spin_unlock(&ipc->lock, key); + + /* Wake the user thread */ + k_event_set(pdata->event, IPC_USER_EVENT_CMD); + + /* Wait for user thread to complete */ + ret = k_sem_take(pdata->sem, K_MSEC(10)); + if (ret) { + LOG_ERR("IPC user: sem error %d\n", ret); + return ret; + } + + /* Clear the task mask bit and check for completion */ + key = k_spin_lock(&ipc->lock); + ipc->task_mask &= ~IPC_TASK_IN_THREAD; + ipc_complete_cmd(ipc); + k_spin_unlock(&ipc->lock, key); + + return pdata->result; +} + +/** + * User-space thread entry point for pipeline_two_components test. + * p1 points to the ppl_test_ctx shared with the kernel launcher. + */ +static void ipc_user_thread_fn(void *p1, void *p2, void *p3) +{ + struct ipc_user *ipc_user = p1; + + ARG_UNUSED(p2); + ARG_UNUSED(p3); + + __ASSERT(k_is_user_context(), "expected user context"); + + /* Signal startup complete — unblocks init waiting on semaphore */ + k_sem_give(ipc_user->sem); + LOG_INF("IPC user-space thread started"); + + for (;;) { + uint32_t mask = k_event_wait_safe(ipc_user->event, + IPC_USER_EVENT_CMD | IPC_USER_EVENT_STOP, + false, K_MSEC(5000)); + + LOG_DBG("IPC user wake, mask %u", mask); + + if (mask & IPC_USER_EVENT_CMD) { + struct ipc4_pipeline_create pipe_msg; + + /* Reconstruct the IPC4 message from copied words */ + pipe_msg.primary.dat = ipc_user->ipc_msg_pri; + pipe_msg.extension.dat = ipc_user->ipc_msg_ext; + + /* Execute pipeline creation in user context */ + ipc_user->result = ipc_pipeline_new(ipc_user->ipc, (ipc_pipe_new *)&pipe_msg); + + /* Signal completion — kernel side will finish IPC */ + k_sem_give(ipc_user->sem); + } + + if (mask & IPC_USER_EVENT_STOP) + break; + } +} + +__cold int ipc_user_init(void) +{ + struct ipc *ipc = ipc_get(); + struct ipc_user *ipc_user = sof_heap_alloc(sof_sys_user_heap_get(), SOF_MEM_FLAG_USER, + sizeof(*ipc_user), 0); + int ret; + + ipc_user->sem = k_object_alloc(K_OBJ_SEM); + if (!ipc_user->sem) { + LOG_ERR("user IPC sem alloc failed"); + k_panic(); + } + + ret = k_mem_domain_add_partition(zephyr_ll_mem_domain(), &ipc_context_part); + + k_sem_init(ipc_user->sem, 0, 1); + + /* Allocate kernel objects for the user-space thread */ + ipc_user->event = k_object_alloc(K_OBJ_EVENT); + if (!ipc_user->event) { + LOG_ERR("user IPC event alloc failed"); + k_panic(); + } + k_event_init(ipc_user->event); + + k_thread_create(&ipc_user_thread, ipc_user_stack, IPC_USER_STACKSIZE, + ipc_user_thread_fn, ipc_user, NULL, NULL, + -1, K_USER, K_FOREVER); + + ipc_user->thread = &ipc_user_thread; + k_thread_access_grant(&ipc_user_thread, ipc_user->sem, ipc_user->event); + user_grant_dai_access_all(&ipc_user_thread); + user_grant_dma_access_all(&ipc_user_thread); + user_access_to_mailbox(zephyr_ll_mem_domain(), &ipc_user_thread); + zephyr_ll_grant_access(&ipc_user_thread); + k_mem_domain_add_thread(zephyr_ll_mem_domain(), &ipc_user_thread); + + k_thread_name_set(&ipc_user_thread, __func__); + + /* Store references in ipc struct so kernel handler can forward commands */ + ipc->ipc_user_pdata = ipc_user; + + k_thread_start(&ipc_user_thread); + + struct task *task = zephyr_ll_task_alloc(); + schedule_task_init_ll(task, SOF_UUID(ipc_uuid), SOF_SCHEDULE_LL_TIMER, + 0, NULL, NULL, cpu_get_id(), 0); + ipc_user->audio_thread = scheduler_init_context(task); + + /* Wait for user thread startup — consumes the initial k_sem_give from thread */ + k_sem_take(ipc->ipc_user_pdata->sem, K_FOREVER); + + return 0; +} +#else +static int ipc_user_init(void) +{ + return 0; +} +#endif /* CONFIG_SOF_USERSPACE_LL */ + __cold int ipc_init(struct sof *sof) { + struct k_heap *heap; + struct ipc *ipc; + assert_can_be_cold(); tr_dbg(&ipc_tr, "entry"); +#ifdef CONFIG_SOF_USERSPACE_LL + heap = zephyr_ll_user_heap(); + + ipc = ipc_get(); + memset(ipc, 0, sizeof(*ipc)); +#else + heap = NULL; + /* init ipc data */ - sof->ipc = rzalloc(SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, sizeof(*sof->ipc)); - if (!sof->ipc) { + ipc = rzalloc(SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, sizeof(*ipc)); + if (!ipc) { tr_err(&ipc_tr, "Unable to allocate IPC data"); return -ENOMEM; } - sof->ipc->comp_data = rzalloc(SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, - SOF_IPC_MSG_MAX_SIZE); - if (!sof->ipc->comp_data) { + sof->ipc = ipc; +#endif + + ipc->comp_data = sof_heap_alloc(heap, SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, + SOF_IPC_MSG_MAX_SIZE, 0); + if (!ipc->comp_data) { tr_err(&ipc_tr, "Unable to allocate IPC component data"); - rfree(sof->ipc); + sof_heap_free(heap, ipc); return -ENOMEM; } + memset(ipc->comp_data, 0, SOF_IPC_MSG_MAX_SIZE); - k_spinlock_init(&sof->ipc->lock); - list_init(&sof->ipc->msg_list); - list_init(&sof->ipc->comp_list); + k_spinlock_init(&ipc->lock); + list_init(&ipc->msg_list); + list_init(&ipc->comp_list); #ifdef CONFIG_SOF_TELEMETRY_IO_PERFORMANCE_MEASUREMENTS struct io_perf_data_item init_data = {IO_PERF_IPC_ID, @@ -319,20 +526,17 @@ __cold int ipc_init(struct sof *sof) IO_PERF_POWERED_UP_ENABLED, IO_PERF_D0IX_POWER_MODE, 0, 0, 0 }; - io_perf_monitor_init_data(&sof->ipc->io_perf_in_msg_count, &init_data); + io_perf_monitor_init_data(&ipc->io_perf_in_msg_count, &init_data); init_data.direction = IO_PERF_OUTPUT_DIRECTION; - io_perf_monitor_init_data(&sof->ipc->io_perf_out_msg_count, &init_data); + io_perf_monitor_init_data(&ipc->io_perf_out_msg_count, &init_data); #endif -#if CONFIG_SOF_BOOT_TEST_STANDALONE - LOG_INF("SOF_BOOT_TEST_STANDALONE, disabling IPC."); - return 0; -#endif #ifdef __ZEPHYR__ - struct k_thread *thread = &sof->ipc->ipc_send_wq.thread; + struct k_thread *thread = &ipc->ipc_send_wq.thread; - k_work_queue_start(&sof->ipc->ipc_send_wq, ipc_send_wq_stack, + k_work_queue_init(&ipc->ipc_send_wq); + k_work_queue_start(&ipc->ipc_send_wq, ipc_send_wq_stack, K_THREAD_STACK_SIZEOF(ipc_send_wq_stack), 1, NULL); k_thread_suspend(thread); @@ -342,10 +546,17 @@ __cold int ipc_init(struct sof *sof) k_thread_resume(thread); - k_work_init_delayable(&sof->ipc->z_delayed_work, ipc_work_handler); + k_work_init_delayable(&ipc->z_delayed_work, ipc_work_handler); +#endif + + ipc_user_init(); + +#if CONFIG_SOF_BOOT_TEST_STANDALONE + LOG_INF("SOF_BOOT_TEST_STANDALONE, skipping platform IPC init."); + return 0; #endif - return platform_ipc_init(sof->ipc); + return platform_ipc_init(ipc); } /* Locking: call with ipc->lock held and interrupts disabled */ diff --git a/src/ipc/ipc4/handler-user.c b/src/ipc/ipc4/handler-user.c index c3ba63b3d267..32fd2557419d 100644 --- a/src/ipc/ipc4/handler-user.c +++ b/src/ipc/ipc4/handler-user.c @@ -90,6 +90,7 @@ static inline const struct ipc4_pipeline_set_state_data *ipc4_get_pipeline_data( /* * Global IPC Operations. */ +#ifndef CONFIG_SOF_USERSPACE_LL __cold static int ipc4_new_pipeline(struct ipc4_message_request *ipc4) { struct ipc *ipc = ipc_get(); @@ -98,6 +99,7 @@ __cold static int ipc4_new_pipeline(struct ipc4_message_request *ipc4) return ipc_pipeline_new(ipc, (ipc_pipe_new *)ipc4); } +#endif __cold static int ipc4_delete_pipeline(struct ipc4_message_request *ipc4) { @@ -666,7 +668,12 @@ int ipc4_user_process_glb_message(struct ipc4_message_request *ipc4, /* pipeline settings */ case SOF_IPC4_GLB_CREATE_PIPELINE: + /* Implementation in progress: forward only CREATE_PIPELINE for now */ +#ifdef CONFIG_SOF_USERSPACE_LL + ret = ipc_user_forward_cmd(ipc4); +#else ret = ipc4_new_pipeline(ipc4); +#endif break; case SOF_IPC4_GLB_DELETE_PIPELINE: ret = ipc4_delete_pipeline(ipc4); diff --git a/src/ipc/ipc4/helper.c b/src/ipc/ipc4/helper.c index 106f4fa4257b..c4c398c1b13f 100644 --- a/src/ipc/ipc4/helper.c +++ b/src/ipc/ipc4/helper.c @@ -340,9 +340,16 @@ __cold static int ipc4_create_pipeline(struct ipc4_pipeline_create *pipe_desc, struct ipc_comp_dev *ipc_pipe; struct pipeline *pipe; struct ipc *ipc = ipc_get(); + struct k_heap *heap = NULL; assert_can_be_cold(); +#ifdef CONFIG_SOF_USERSPACE_LL + heap = zephyr_ll_user_heap(); +#endif + + LOG_INF("pipe_desc %x, instance %u", pipe_desc, pipe_desc->primary.r.instance_id); + /* check whether pipeline id is already taken or in use */ ipc_pipe = ipc_get_pipeline_by_id(ipc, pipe_desc->primary.r.instance_id); if (ipc_pipe) { @@ -352,8 +359,9 @@ __cold static int ipc4_create_pipeline(struct ipc4_pipeline_create *pipe_desc, } /* create the pipeline */ - pipe = pipeline_new(NULL, pipe_desc->primary.r.instance_id, + pipe = pipeline_new(heap, pipe_desc->primary.r.instance_id, pipe_desc->primary.r.ppl_priority, 0, pparams); + LOG_INF("pipeline_new() -> %p", pipe); if (!pipe) { tr_err(&ipc_tr, "ipc: pipeline_new() failed"); return IPC4_OUT_OF_MEMORY; @@ -368,12 +376,13 @@ __cold static int ipc4_create_pipeline(struct ipc4_pipeline_create *pipe_desc, pipe->core = pipe_desc->extension.r.core_id; /* allocate the IPC pipeline container */ - ipc_pipe = rzalloc(SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, - sizeof(struct ipc_comp_dev)); + ipc_pipe = sof_heap_alloc(heap, SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, + sizeof(struct ipc_comp_dev), 0); if (!ipc_pipe) { pipeline_free(pipe); return IPC4_OUT_OF_MEMORY; } + memset(ipc_pipe, 0, sizeof(*ipc_pipe)); ipc_pipe->pipeline = pipe; ipc_pipe->type = COMP_TYPE_PIPELINE; @@ -384,6 +393,8 @@ __cold static int ipc4_create_pipeline(struct ipc4_pipeline_create *pipe_desc, /* add new pipeline to the list */ list_item_append(&ipc_pipe->list, &ipc->comp_list); + LOG_INF("success"); + return IPC4_SUCCESS; } @@ -715,6 +726,14 @@ __cold int ipc_comp_connect(struct ipc *ipc, ipc_pipe_comp_connect *_connect) #else dp_heap = NULL; #endif /* CONFIG_ZEPHYR_DP_SCHEDULER */ + +#ifdef CONFIG_SOF_USERSPACE_LL + if (!dp_heap) { + /* use system user heap for non-DP module buffers */ + dp_heap = sof_sys_user_heap_get(); + } +#endif + bool cross_core_bind = source->ipc_config.core != sink->ipc_config.core; /* If both components are on same core -- process IPC on that core, diff --git a/zephyr/include/rtos/sof.h b/zephyr/include/rtos/sof.h index 573e6ac63d77..efe0c1e6acab 100644 --- a/zephyr/include/rtos/sof.h +++ b/zephyr/include/rtos/sof.h @@ -46,8 +46,10 @@ struct sof { int argc; char **argv; +#ifndef CONFIG_SOF_USERSPACE_LL /* ipc */ struct ipc *ipc; +#endif /* system agent */ struct sa *sa; From af6a001a4bd70526ff1f8a15b62f120948825fe5 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Mon, 30 Mar 2026 20:06:52 +0300 Subject: [PATCH 080/106] WIP: ipc: ipc4: route PIPELINE_DELETE to user-space handler Incremental change to also handle PIPELINE_DELETE IPC in the user-space IPC thread. Signed-off-by: Kai Vehmanen --- src/ipc/ipc-common.c | 29 +++++++++++++++++++++++------ src/ipc/ipc4/handler-user.c | 6 ++++++ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/ipc/ipc-common.c b/src/ipc/ipc-common.c index c620f005be22..ee61b04f3c95 100644 --- a/src/ipc/ipc-common.c +++ b/src/ipc/ipc-common.c @@ -401,14 +401,31 @@ static void ipc_user_thread_fn(void *p1, void *p2, void *p3) LOG_DBG("IPC user wake, mask %u", mask); if (mask & IPC_USER_EVENT_CMD) { - struct ipc4_pipeline_create pipe_msg; + struct ipc4_message_request msg; /* Reconstruct the IPC4 message from copied words */ - pipe_msg.primary.dat = ipc_user->ipc_msg_pri; - pipe_msg.extension.dat = ipc_user->ipc_msg_ext; - - /* Execute pipeline creation in user context */ - ipc_user->result = ipc_pipeline_new(ipc_user->ipc, (ipc_pipe_new *)&pipe_msg); + msg.primary.dat = ipc_user->ipc_msg_pri; + msg.extension.dat = ipc_user->ipc_msg_ext; + + switch (msg.primary.r.type) { + case SOF_IPC4_GLB_CREATE_PIPELINE: + ipc_user->result = ipc_pipeline_new(ipc_user->ipc, + (ipc_pipe_new *)&msg); + break; + case SOF_IPC4_GLB_DELETE_PIPELINE: { + struct ipc4_pipeline_delete *pipe = + (struct ipc4_pipeline_delete *)&msg; + + ipc_user->result = ipc_pipeline_free( + ipc_user->ipc, pipe->primary.r.instance_id); + break; + } + default: + LOG_ERR("IPC user: unsupported cmd type %d", + msg.primary.r.type); + ipc_user->result = -EINVAL; + break; + } /* Signal completion — kernel side will finish IPC */ k_sem_give(ipc_user->sem); diff --git a/src/ipc/ipc4/handler-user.c b/src/ipc/ipc4/handler-user.c index 32fd2557419d..78fa3b6c2633 100644 --- a/src/ipc/ipc4/handler-user.c +++ b/src/ipc/ipc4/handler-user.c @@ -101,6 +101,7 @@ __cold static int ipc4_new_pipeline(struct ipc4_message_request *ipc4) } #endif +#ifndef CONFIG_SOF_USERSPACE_LL __cold static int ipc4_delete_pipeline(struct ipc4_message_request *ipc4) { struct ipc4_pipeline_delete *pipe; @@ -113,6 +114,7 @@ __cold static int ipc4_delete_pipeline(struct ipc4_message_request *ipc4) return ipc_pipeline_free(ipc, pipe->primary.r.instance_id); } +#endif static int ipc4_pcm_params(struct ipc_comp_dev *pcm_dev) { @@ -676,7 +678,11 @@ int ipc4_user_process_glb_message(struct ipc4_message_request *ipc4, #endif break; case SOF_IPC4_GLB_DELETE_PIPELINE: +#ifdef CONFIG_SOF_USERSPACE_LL + ret = ipc_user_forward_cmd(ipc4); +#else ret = ipc4_delete_pipeline(ipc4); +#endif break; case SOF_IPC4_GLB_SET_PIPELINE_STATE: ret = ipc4_set_pipeline_state(ipc4); From a1d65c36611c6c7fb8340cb4ed3c6e226141fdd0 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Wed, 1 Apr 2026 13:58:09 +0300 Subject: [PATCH 081/106] ipc: make IPC stack thread size configurable Make IPC user thread stack size configurable with SOF_IPC_USER_THREAD_STACK_SIZE (default 4096). Also pin the IPC user thread on primar CPU. Signed-off-by: Jyri Sarha Signed-off-by: Kai Vehmanen --- src/ipc/ipc-common.c | 9 +++++---- zephyr/Kconfig | 8 ++++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/ipc/ipc-common.c b/src/ipc/ipc-common.c index ee61b04f3c95..2fe89fd77caf 100644 --- a/src/ipc/ipc-common.c +++ b/src/ipc/ipc-common.c @@ -318,13 +318,12 @@ void ipc_schedule_process(struct ipc *ipc) #ifdef CONFIG_SOF_USERSPACE_LL /* User-space thread for pipeline_two_components test */ -#define IPC_USER_STACKSIZE 8192 #define IPC_USER_EVENT_CMD BIT(0) #define IPC_USER_EVENT_STOP BIT(1) static struct k_thread ipc_user_thread; -static K_THREAD_STACK_DEFINE(ipc_user_stack, IPC_USER_STACKSIZE); +static K_THREAD_STACK_DEFINE(ipc_user_stack, CONFIG_SOF_IPC_USER_THREAD_STACK_SIZE); /** * @brief Forward an IPC4 command to the user-space thread. @@ -461,7 +460,8 @@ __cold int ipc_user_init(void) } k_event_init(ipc_user->event); - k_thread_create(&ipc_user_thread, ipc_user_stack, IPC_USER_STACKSIZE, + k_thread_create(&ipc_user_thread, ipc_user_stack, + CONFIG_SOF_IPC_USER_THREAD_STACK_SIZE, ipc_user_thread_fn, ipc_user, NULL, NULL, -1, K_USER, K_FOREVER); @@ -473,7 +473,8 @@ __cold int ipc_user_init(void) zephyr_ll_grant_access(&ipc_user_thread); k_mem_domain_add_thread(zephyr_ll_mem_domain(), &ipc_user_thread); - k_thread_name_set(&ipc_user_thread, __func__); + k_thread_cpu_pin(&ipc_user_thread, PLATFORM_PRIMARY_CORE_ID); + k_thread_name_set(&ipc_user_thread, "ipc_user"); /* Store references in ipc struct so kernel handler can forward commands */ ipc->ipc_user_pdata = ipc_user; diff --git a/zephyr/Kconfig b/zephyr/Kconfig index d494da791590..2fbd349fa809 100644 --- a/zephyr/Kconfig +++ b/zephyr/Kconfig @@ -279,3 +279,11 @@ config STACK_SIZE_IPC_TX default 2048 help IPC sender work-queue thread stack size. Keep a power of 2. + +config SOF_IPC_USER_THREAD_STACK_SIZE + int "IPC user thread stack size" + default 8192 + depends on SOF_USERSPACE_LL + help + Stack size for the userspace IPC thread. + Keep a power of 2. From 30ceb03044a0fcf071027d8ae3056a46913878cc Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 19 Mar 2026 14:49:50 +0200 Subject: [PATCH 082/106] ipc: ipc4: use sof_heap_alloc in ipc4_add_comp_dev() Use sof_heap_alloc() and the new sof_sys_user_heap_get() to route the allocs to correct heap based on SOF build options. If the audio pipeline code is run completely in user-space, a compatible heap should be used. Signed-off-by: Kai Vehmanen --- src/ipc/ipc4/helper.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ipc/ipc4/helper.c b/src/ipc/ipc4/helper.c index c4c398c1b13f..d4d2459392a1 100644 --- a/src/ipc/ipc4/helper.c +++ b/src/ipc/ipc4/helper.c @@ -1357,13 +1357,14 @@ __cold static int ipc4_add_comp_dev(struct comp_dev *dev) } /* allocate the IPC component container */ - icd = rzalloc(SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, - sizeof(struct ipc_comp_dev)); + icd = sof_heap_alloc(sof_sys_user_heap_get(), SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, + sizeof(struct ipc_comp_dev), 0); if (!icd) { tr_err(&ipc_tr, "alloc failed"); - rfree(icd); + sof_heap_free(sof_sys_user_heap_get(), icd); return IPC4_OUT_OF_MEMORY; } + memset(icd, 0, sizeof(*icd)); icd->cd = dev; icd->type = COMP_TYPE_COMPONENT; From ec3884030d9645e1824da128dffb38e4f7e753a5 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Mon, 30 Mar 2026 17:14:33 +0300 Subject: [PATCH 083/106] ipc: turn ipc_msg_reply() into a system call Make ipc_msg_reply() a system call, so it can be called from audio thread even when audio thread is running in a user-space thread. Signed-off-by: Kai Vehmanen --- src/include/sof/ipc/common.h | 2 +- src/include/sof/ipc/ipc_reply.h | 28 ++++++++++++++++++++++++++++ src/ipc/ipc3/helper.c | 2 +- src/ipc/ipc4/handler-kernel.c | 16 +++++++++++++++- zephyr/CMakeLists.txt | 1 + 5 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 src/include/sof/ipc/ipc_reply.h diff --git a/src/include/sof/ipc/common.h b/src/include/sof/ipc/common.h index 6727175aa4b4..7fa6e1977a26 100644 --- a/src/include/sof/ipc/common.h +++ b/src/include/sof/ipc/common.h @@ -310,7 +310,7 @@ int ipc_process_on_core(uint32_t core, bool blocking); * \brief reply to an IPC message. * @param[in] reply pointer to the reply structure. */ -void ipc_msg_reply(struct sof_ipc_reply *reply); +#include /** * \brief Call platform-specific IPC completion function. diff --git a/src/include/sof/ipc/ipc_reply.h b/src/include/sof/ipc/ipc_reply.h new file mode 100644 index 000000000000..756fd535ca30 --- /dev/null +++ b/src/include/sof/ipc/ipc_reply.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2026 Intel Corporation. All rights reserved. + */ + +#ifndef __SOF_IPC_IPC_REPLY_H__ +#define __SOF_IPC_IPC_REPLY_H__ + +#include + +struct sof_ipc_reply; + +/** + * \brief reply to an IPC message. + * @param[in] reply pointer to the reply structure. + */ +#if defined(__ZEPHYR__) && defined(CONFIG_SOF_USERSPACE_LL) +__syscall void ipc_msg_reply(struct sof_ipc_reply *reply); +#else +void z_impl_ipc_msg_reply(struct sof_ipc_reply *reply); +#define ipc_msg_reply z_impl_ipc_msg_reply +#endif + +#if defined(__ZEPHYR__) && defined(CONFIG_SOF_USERSPACE_LL) +#include +#endif + +#endif /* __SOF_IPC_IPC_REPLY_H__ */ diff --git a/src/ipc/ipc3/helper.c b/src/ipc/ipc3/helper.c index 4d87f042dd1d..fd2af4603b29 100644 --- a/src/ipc/ipc3/helper.c +++ b/src/ipc/ipc3/helper.c @@ -710,7 +710,7 @@ int ipc_comp_new(struct ipc *ipc, ipc_comp *_comp) return 0; } -void ipc_msg_reply(struct sof_ipc_reply *reply) +void z_impl_ipc_msg_reply(struct sof_ipc_reply *reply) { struct ipc *ipc = ipc_get(); k_spinlock_key_t key; diff --git a/src/ipc/ipc4/handler-kernel.c b/src/ipc/ipc4/handler-kernel.c index ae75ebc87303..a4613adc1fae 100644 --- a/src/ipc/ipc4/handler-kernel.c +++ b/src/ipc/ipc4/handler-kernel.c @@ -41,6 +41,11 @@ #include #include +#ifdef __ZEPHYR__ +#include +#include +#endif + #if CONFIG_SOF_BOOT_TEST /* CONFIG_SOF_BOOT_TEST depends on Zephyr */ #include @@ -515,7 +520,7 @@ void ipc_send_buffer_status_notify(void) } #endif -void ipc_msg_reply(struct sof_ipc_reply *reply) +void z_impl_ipc_msg_reply(struct sof_ipc_reply *reply) { struct ipc4_message_request in; @@ -523,6 +528,15 @@ void ipc_msg_reply(struct sof_ipc_reply *reply) ipc_compound_msg_done(in.primary.r.type, reply->error); } +#ifdef CONFIG_USERSPACE +void z_vrfy_ipc_msg_reply(struct sof_ipc_reply *reply) +{ + K_OOPS(K_SYSCALL_MEMORY_READ(reply, sizeof(*reply))); + z_impl_ipc_msg_reply(reply); +} +#include +#endif + void ipc_cmd(struct ipc_cmd_hdr *_hdr) { struct ipc4_message_request *in = ipc4_get_message_request(); diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index cb5bbef0239c..ee4091d52f48 100644 --- a/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -549,6 +549,7 @@ 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(${SOF_SRC_PATH}/include/sof/ipc/ipc_reply.h) zephyr_syscall_header(include/rtos/alloc.h) zephyr_library_sources(syscall/alloc.c) zephyr_syscall_header(${SOF_SRC_PATH}/include/sof/lib/dai-zephyr.h) From 0dcb5548027ab9c7acae4e0babf79cafaff128b5 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 19 Mar 2026 21:39:40 +0200 Subject: [PATCH 084/106] ipc: ipc4: use correct API to get DMA status Use sof_dma_get_status() call to allow the audio pipeline to be run in user-space. Signed-off-by: Kai Vehmanen --- src/ipc/ipc4/dai.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ipc/ipc4/dai.c b/src/ipc/ipc4/dai.c index c2b63f1f9a73..373671d47594 100644 --- a/src/ipc/ipc4/dai.c +++ b/src/ipc/ipc4/dai.c @@ -466,7 +466,7 @@ void dai_dma_position_update(struct dai_data *dd, struct comp_dev *dev) if (!dd->slot_info.node_id) return; - ret = dma_get_status(dd->dma->z_dev, dd->chan_index, &status); + ret = sof_dma_get_status(dd->dma, dd->chan_index, &status); if (ret < 0) return; From 2e6c52efd889a426aab6ab354826e723c8963fc2 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 16 Apr 2026 14:31:09 +0300 Subject: [PATCH 085/106] ipc: ipc4: dai: fix direct use of DMA driver calls Fix a few remaining uses of direct DMA driver calls. Use the sof_dma.h wrapper instead, allowing the code to be used also from user-space. Signed-off-by: Kai Vehmanen --- src/ipc/ipc4/dai.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ipc/ipc4/dai.c b/src/ipc/ipc4/dai.c index 373671d47594..9ead337a8984 100644 --- a/src/ipc/ipc4/dai.c +++ b/src/ipc/ipc4/dai.c @@ -245,9 +245,9 @@ void dai_dma_release(struct dai_data *dd, struct comp_dev *dev) */ #if CONFIG_ZEPHYR_NATIVE_DRIVERS /* if reset is after pause dma has already been stopped */ - dma_stop(dd->dma->z_dev, dd->chan_index); + sof_dma_stop(dd->dma, dd->chan_index); - dma_release_channel(dd->dma->z_dev, dd->chan_index); + sof_dma_release_channel(dd->dma, dd->chan_index); #else /* TODO: to remove this, no longer works! */ dma_stop_legacy(dd->chan); From cd8b1dd42c337aedb241d6bf377cbc189d29d5be Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Fri, 20 Mar 2026 21:44:59 +0200 Subject: [PATCH 086/106] ipc: use application heap for IPC pipeline and component allocations Replace rfree() calls with sof_heap_free() using the system user heap for IPC component and pipeline objects. In ipc4_create_pipeline(), replace the conditional zephyr_ll_user_heap() with the generic sof_sys_user_heap_get() to unify the allocation path across configurations. Signed-off-by: Kai Vehmanen --- src/ipc/ipc-helper.c | 2 +- src/ipc/ipc4/helper.c | 10 +++------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/ipc/ipc-helper.c b/src/ipc/ipc-helper.c index 102f29276cc1..44465bbfc054 100644 --- a/src/ipc/ipc-helper.c +++ b/src/ipc/ipc-helper.c @@ -369,7 +369,7 @@ __cold int ipc_comp_free(struct ipc *ipc, uint32_t comp_id) icd->cd = NULL; list_item_del(&icd->list); - rfree(icd); + sof_heap_free(sof_sys_user_heap_get(), icd); return 0; } diff --git a/src/ipc/ipc4/helper.c b/src/ipc/ipc4/helper.c index d4d2459392a1..6d2f60ac4c66 100644 --- a/src/ipc/ipc4/helper.c +++ b/src/ipc/ipc4/helper.c @@ -340,14 +340,10 @@ __cold static int ipc4_create_pipeline(struct ipc4_pipeline_create *pipe_desc, struct ipc_comp_dev *ipc_pipe; struct pipeline *pipe; struct ipc *ipc = ipc_get(); - struct k_heap *heap = NULL; + struct k_heap *heap = sof_sys_user_heap_get(); assert_can_be_cold(); -#ifdef CONFIG_SOF_USERSPACE_LL - heap = zephyr_ll_user_heap(); -#endif - LOG_INF("pipe_desc %x, instance %u", pipe_desc, pipe_desc->primary.r.instance_id); /* check whether pipeline id is already taken or in use */ @@ -561,7 +557,7 @@ __cold int ipc_pipeline_free(struct ipc *ipc, uint32_t comp_id) ipc_pipe->pipeline = NULL; list_item_del(&ipc_pipe->list); - rfree(ipc_pipe); + sof_heap_free(sof_sys_user_heap_get(), ipc_pipe); return IPC4_SUCCESS; } @@ -1122,7 +1118,7 @@ __cold int ipc4_chain_dma_state(struct comp_dev *dev, struct ipc4_chain_dma *cdm if (icd->cd != dev) continue; list_item_del(&icd->list); - rfree(icd); + sof_heap_free(sof_sys_user_heap_get(), icd); break; } comp_free(dev); From dc7ba1690c1ba7fe12fa0d5a13f5c73be64746d1 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Wed, 15 Apr 2026 15:55:44 +0300 Subject: [PATCH 087/106] ipc4: make fw_reg mutex available to user-space Mark the fw_reg mutex with APP_SYSUSER_BSS, allowing the lock to be used when DAI module is run in user-space. Signed-off-by: Kai Vehmanen --- src/ipc/ipc4/dai.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ipc/ipc4/dai.c b/src/ipc/ipc4/dai.c index 9ead337a8984..ad225ef340e6 100644 --- a/src/ipc/ipc4/dai.c +++ b/src/ipc/ipc4/dai.c @@ -34,8 +34,7 @@ LOG_MODULE_DECLARE(ipc, CONFIG_SOF_LOG_LEVEL); -/* Protects IPC4 LLP reading-slot firmware registers used by DAI code. */ -static SYS_MUTEX_DEFINE(llp_reading_slots_lock); +static APP_SYSUSER_BSS SYS_MUTEX_DEFINE(llp_reading_slots_lock); void dai_set_link_hda_config(uint16_t *link_config, struct ipc_config_dai *common_config, From b4088eec5039bcd2d20e0e30664a673a6545acfd Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Tue, 31 Mar 2026 14:45:40 +0300 Subject: [PATCH 088/106] ipc: ipc4: helper: make ipc4_search_for_drv() userspace compatible Do not use IRQ disable/enable when built for user-space. The driver list is immutable by IPC processing time so no lock is needed. Signed-off-by: Kai Vehmanen --- src/ipc/ipc4/helper.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/ipc/ipc4/helper.c b/src/ipc/ipc4/helper.c index 6d2f60ac4c66..be1facef079f 100644 --- a/src/ipc/ipc4/helper.c +++ b/src/ipc/ipc4/helper.c @@ -1216,11 +1216,19 @@ __cold static const struct comp_driver *ipc4_search_for_drv(const void *uuid) struct list_item *clist; const struct comp_driver *drv = NULL; struct comp_driver_info *info; +#ifndef CONFIG_SOF_USERSPACE_LL uint32_t flags; +#endif assert_can_be_cold(); + /* Driver list is populated at boot before IPC processing starts. + * In user-space builds irq_local_disable() is privileged, but the + * list is immutable by this point so no lock is needed. + */ +#ifndef CONFIG_SOF_USERSPACE_LL irq_local_disable(flags); +#endif /* search driver list with UUID */ list_for_item(clist, &drivers->list) { @@ -1236,7 +1244,9 @@ __cold static const struct comp_driver *ipc4_search_for_drv(const void *uuid) } } +#ifndef CONFIG_SOF_USERSPACE_LL irq_local_enable(flags); +#endif return drv; } From b861ac86ead73977e7a5561efec152222845a2df Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Wed, 22 Apr 2026 15:24:31 +0300 Subject: [PATCH 089/106] WIP: schedule: add multi-core support for user-space LL scheduler - add arch_schedulers_get_for_core() for task-carried core routing - route 8 task-aware scheduler callers via task->core under CONFIG_SOF_USERSPACE_LL - fix zephyr_domain_thread_tid() to accept core parameter instead of hardcoding core 0 - create domain threads for secondary cores in secondary_core_init() - remove FIXME workaround from arch_schedulers_get() Signed-off-by: Kai Vehmanen --- src/audio/pipeline/pipeline-schedule.c | 6 +- src/include/sof/schedule/ll_schedule_domain.h | 6 +- src/include/sof/schedule/schedule.h | 61 +++++++++++++++++++ src/init/init.c | 16 +++++ src/ipc/ipc4/helper.c | 8 +-- src/schedule/zephyr_domain.c | 5 +- src/schedule/zephyr_ll.c | 25 +++++--- uuid-registry.txt | 1 + zephyr/schedule.c | 20 ++++-- 9 files changed, 123 insertions(+), 25 deletions(-) diff --git a/src/audio/pipeline/pipeline-schedule.c b/src/audio/pipeline/pipeline-schedule.c index 7e19b43963ff..cddfdc7165db 100644 --- a/src/audio/pipeline/pipeline-schedule.c +++ b/src/audio/pipeline/pipeline-schedule.c @@ -291,7 +291,9 @@ void pipeline_schedule_triggered(struct pipeline_walk_context *ctx, * while pipeline state is being updated. The k_mutex is re-entrant * so schedule_task() calls inside the critical section are safe. */ - zephyr_ll_lock_sched(); + int sched_core = ppl_data->start->ipc_config.core; + + zephyr_ll_lock_sched(sched_core); #else uint32_t flags; @@ -358,7 +360,7 @@ void pipeline_schedule_triggered(struct pipeline_walk_context *ctx, } } #ifdef CONFIG_SOF_USERSPACE_LL - zephyr_ll_unlock_sched(); + zephyr_ll_unlock_sched(sched_core); #else irq_local_enable(flags); #endif diff --git a/src/include/sof/schedule/ll_schedule_domain.h b/src/include/sof/schedule/ll_schedule_domain.h index 639dcb5caea6..262a03139f32 100644 --- a/src/include/sof/schedule/ll_schedule_domain.h +++ b/src/include/sof/schedule/ll_schedule_domain.h @@ -117,8 +117,8 @@ void zephyr_ll_task_free(struct task *task); struct k_heap *zephyr_ll_user_heap(void); void zephyr_ll_user_resources_init(void); void zephyr_ll_grant_access(struct k_thread *thread); -void zephyr_ll_lock_sched(void); -void zephyr_ll_unlock_sched(void); +void zephyr_ll_lock_sched(int core); +void zephyr_ll_unlock_sched(int core); #endif /* CONFIG_SOF_USERSPACE_LL */ static inline struct ll_schedule_domain *domain_init @@ -317,7 +317,7 @@ struct ll_schedule_domain *zephyr_ll_domain(void); struct ll_schedule_domain *zephyr_domain_init(int clk); #define timer_domain_init(timer, clk) zephyr_domain_init(clk) #ifdef CONFIG_SOF_USERSPACE_LL -struct k_thread *zephyr_domain_thread_tid(struct ll_schedule_domain *domain); +struct k_thread *zephyr_domain_thread_tid(struct ll_schedule_domain *domain, int core); struct k_mem_domain *zephyr_ll_mem_domain(void); #endif /* CONFIG_SOF_USERSPACE_LL */ #endif /* __ZEPHYR__ */ diff --git a/src/include/sof/schedule/schedule.h b/src/include/sof/schedule/schedule.h index 9925e8d9b273..c18e9d72f277 100644 --- a/src/include/sof/schedule/schedule.h +++ b/src/include/sof/schedule/schedule.h @@ -198,6 +198,10 @@ struct schedulers { */ struct schedulers **arch_schedulers_get(void); +#if CONFIG_SOF_USERSPACE_LL +struct schedulers **arch_schedulers_get_for_core(int core); +#endif + /** * Retrieves scheduler's data. * @param type SOF_SCHEDULE_ type. @@ -218,10 +222,39 @@ static inline void *scheduler_get_data(uint16_t type) return NULL; } +#if CONFIG_SOF_USERSPACE_LL +/** + * Retrieves scheduler's data for a specific core. + * @param type SOF_SCHEDULE_ type. + * @param core Core ID to get scheduler data for. + * @return Pointer to scheduler's data. + * + * Safe to call from user-space context — does not use cpu_get_id(). + */ +static inline void *scheduler_get_data_for_core(uint16_t type, int core) +{ + struct schedulers *schedulers = *arch_schedulers_get_for_core(core); + struct schedule_data *sch; + struct list_item *slist; + + list_for_item(slist, &schedulers->list) { + sch = container_of(slist, struct schedule_data, list); + if (type == sch->type) + return sch->data; + } + + return NULL; +} +#endif + /** See scheduler_ops::schedule_task_running */ static inline int schedule_task_running(struct task *task) { +#if CONFIG_SOF_USERSPACE_LL + struct schedulers *schedulers = *arch_schedulers_get_for_core(task->core); +#else struct schedulers *schedulers = *arch_schedulers_get(); +#endif struct schedule_data *sch; struct list_item *slist; @@ -243,7 +276,11 @@ static inline int schedule_task_running(struct task *task) static inline int schedule_task(struct task *task, uint64_t start, uint64_t period) { +#if CONFIG_SOF_USERSPACE_LL + struct schedulers *schedulers = *arch_schedulers_get_for_core(task->core); +#else struct schedulers *schedulers = *arch_schedulers_get(); +#endif struct schedule_data *sch; struct list_item *slist; @@ -264,7 +301,11 @@ static inline int schedule_task(struct task *task, uint64_t start, static inline int schedule_task_before(struct task *task, uint64_t start, uint64_t period, struct task *before) { +#if CONFIG_SOF_USERSPACE_LL + struct schedulers *schedulers = *arch_schedulers_get_for_core(task->core); +#else struct schedulers *schedulers = *arch_schedulers_get(); +#endif struct schedule_data *sch; struct list_item *slist; @@ -290,7 +331,11 @@ static inline int schedule_task_before(struct task *task, uint64_t start, static inline int schedule_task_after(struct task *task, uint64_t start, uint64_t period, struct task *after) { +#if CONFIG_SOF_USERSPACE_LL + struct schedulers *schedulers = *arch_schedulers_get_for_core(task->core); +#else struct schedulers *schedulers = *arch_schedulers_get(); +#endif struct schedule_data *sch; struct list_item *slist; @@ -315,7 +360,11 @@ static inline int schedule_task_after(struct task *task, uint64_t start, /** See scheduler_ops::reschedule_task */ static inline int reschedule_task(struct task *task, uint64_t start) { +#if CONFIG_SOF_USERSPACE_LL + struct schedulers *schedulers = *arch_schedulers_get_for_core(task->core); +#else struct schedulers *schedulers = *arch_schedulers_get(); +#endif struct schedule_data *sch; struct list_item *slist; @@ -337,7 +386,11 @@ static inline int reschedule_task(struct task *task, uint64_t start) /** See scheduler_ops::schedule_task_cancel */ static inline int schedule_task_cancel(struct task *task) { +#if CONFIG_SOF_USERSPACE_LL + struct schedulers *schedulers = *arch_schedulers_get_for_core(task->core); +#else struct schedulers *schedulers = *arch_schedulers_get(); +#endif struct schedule_data *sch; struct list_item *slist; @@ -353,7 +406,11 @@ static inline int schedule_task_cancel(struct task *task) /** See scheduler_ops::schedule_task_free */ static inline int schedule_task_free(struct task *task) { +#if CONFIG_SOF_USERSPACE_LL + struct schedulers *schedulers = *arch_schedulers_get_for_core(task->core); +#else struct schedulers *schedulers = *arch_schedulers_get(); +#endif struct schedule_data *sch; struct list_item *slist; @@ -402,7 +459,11 @@ static inline int schedulers_restore(void) /** See scheduler_ops::scheduler_init_context */ static inline struct k_thread *scheduler_init_context(struct task *task) { +#if CONFIG_SOF_USERSPACE_LL + struct schedulers *schedulers = *arch_schedulers_get_for_core(task->core); +#else struct schedulers *schedulers = *arch_schedulers_get(); +#endif struct schedule_data *sch; struct list_item *slist; diff --git a/src/init/init.c b/src/init/init.c index e8d374c810ad..58607df1254a 100644 --- a/src/init/init.c +++ b/src/init/init.c @@ -47,6 +47,10 @@ LOG_MODULE_REGISTER(init, CONFIG_SOF_LOG_LEVEL); +#if CONFIG_SOF_USERSPACE_LL +SOF_DEFINE_REG_UUID(sec_core_init); +#endif + /* main firmware context */ static struct sof sof; @@ -135,6 +139,18 @@ __cold int secondary_core_init(struct sof *sof) return err; #endif /* CONFIG_ZEPHYR_DP_SCHEDULER */ +#if CONFIG_SOF_USERSPACE_LL + /* Create domain thread for this secondary core's LL scheduler */ + { + struct task *task = zephyr_ll_task_alloc(); + + schedule_task_init_ll(task, SOF_UUID(sec_core_init_uuid), + SOF_SCHEDULE_LL_TIMER, + 0, NULL, NULL, cpu_get_id(), 0); + scheduler_init_context(task); + } +#endif + /* initialize IDC mechanism */ trace_point(TRACE_BOOT_PLATFORM_IDC); err = idc_init(); diff --git a/src/ipc/ipc4/helper.c b/src/ipc/ipc4/helper.c index be1facef079f..b766e6434f0d 100644 --- a/src/ipc/ipc4/helper.c +++ b/src/ipc/ipc4/helper.c @@ -601,7 +601,7 @@ __cold static struct comp_buffer *ipc4_create_buffer(struct comp_dev *src, bool if (cross_core_bind) \ domain_block(sof_get()->platform_timer_domain); \ else \ - zephyr_ll_lock_sched(); \ + zephyr_ll_lock_sched(cpu_get_id()); \ } while (0) #define ll_unblock(cross_core_bind, flags) \ @@ -609,7 +609,7 @@ __cold static struct comp_buffer *ipc4_create_buffer(struct comp_dev *src, bool if (cross_core_bind) \ domain_unblock(sof_get()->platform_timer_domain); \ else \ - zephyr_ll_unlock_sched(); \ + zephyr_ll_unlock_sched(cpu_get_id()); \ } while (0) #else #define ll_block(cross_core_bind, flags) \ @@ -660,8 +660,8 @@ static int ll_wait_finished_on_core(struct comp_dev *dev) #else #if CONFIG_SOF_USERSPACE_LL -#define ll_block(cross_core_bind, flags) zephyr_ll_lock_sched() -#define ll_unblock(cross_core_bind, flags) zephyr_ll_unlock_sched() +#define ll_block(cross_core_bind, flags) zephyr_ll_lock_sched(cpu_get_id()) +#define ll_unblock(cross_core_bind, flags) zephyr_ll_unlock_sched(cpu_get_id()) #else #define ll_block(cross_core_bind, flags) irq_local_disable(flags) #define ll_unblock(cross_core_bind, flags) irq_local_enable(flags) diff --git a/src/schedule/zephyr_domain.c b/src/schedule/zephyr_domain.c index f77ef40a1933..ff5c82b27d15 100644 --- a/src/schedule/zephyr_domain.c +++ b/src/schedule/zephyr_domain.c @@ -477,13 +477,12 @@ static void zephyr_domain_thread_free(struct ll_schedule_domain *domain, tr_info(&ll_tr, "thread_free done, core %d", core); } -struct k_thread *zephyr_domain_thread_tid(struct ll_schedule_domain *domain) +struct k_thread *zephyr_domain_thread_tid(struct ll_schedule_domain *domain, int core) { struct zephyr_domain *zephyr_domain = ll_sch_domain_get_pdata(domain); - int core = 0; /* cpu_get_id(); */ struct zephyr_domain_thread *dt = zephyr_domain->domain_thread + core; - tr_dbg(&ll_tr, "entry"); + tr_dbg(&ll_tr, "entry core %d", core); return dt->ll_thread; } diff --git a/src/schedule/zephyr_ll.c b/src/schedule/zephyr_ll.c index 2dc5e34c3714..40d4d8e40081 100644 --- a/src/schedule/zephyr_ll.c +++ b/src/schedule/zephyr_ll.c @@ -65,8 +65,15 @@ static void zephyr_ll_unlock(struct zephyr_ll *sch, uint32_t *flags) static void zephyr_ll_assert_core(const struct zephyr_ll *sch) { - assert(CONFIG_CORE_COUNT == 1 || IS_ENABLED(CONFIG_SOF_USERSPACE_LL) || - sch->core == cpu_get_id()); +#if CONFIG_SOF_USERSPACE_LL + /* In user-space mode, cpu_get_id() is not available. + * Core correctness is ensured by task->core routing in + * schedule.h and verified at task schedule time. + */ + (void)sch; +#else + assert(CONFIG_CORE_COUNT == 1 || sch->core == cpu_get_id()); +#endif } /* Locking: caller should hold the domain lock */ @@ -524,10 +531,10 @@ struct k_thread *zephyr_ll_init_context(void *data, struct task *task) if (!k_is_user_context()) { tr_dbg(&ll_tr, "granting access to domain lock %p for thread %p", &sch->ll_domain->lock, - zephyr_domain_thread_tid(sch->ll_domain)); + zephyr_domain_thread_tid(sch->ll_domain, task->core)); } - return zephyr_domain_thread_tid(sch->ll_domain); + return zephyr_domain_thread_tid(sch->ll_domain, task->core); } void zephyr_ll_free_context(void *data) @@ -576,9 +583,10 @@ void zephyr_ll_grant_access(struct k_thread *thread) * schedule_task() calls within the locked section will not deadlock. * Must be paired with zephyr_ll_unlock_sched(). */ -void zephyr_ll_lock_sched(void) +void zephyr_ll_lock_sched(int core) { - struct zephyr_ll *sch = (struct zephyr_ll *)scheduler_get_data(SOF_SCHEDULE_LL_TIMER); + struct zephyr_ll *sch = (struct zephyr_ll *)scheduler_get_data_for_core(SOF_SCHEDULE_LL_TIMER, + core); sys_mutex_lock(&sch->lock, K_FOREVER); } @@ -586,9 +594,10 @@ void zephyr_ll_lock_sched(void) /** * Unlock the LL scheduler after a previous zephyr_ll_lock_sched() call. */ -void zephyr_ll_unlock_sched(void) +void zephyr_ll_unlock_sched(int core) { - struct zephyr_ll *sch = (struct zephyr_ll *)scheduler_get_data(SOF_SCHEDULE_LL_TIMER); + struct zephyr_ll *sch = (struct zephyr_ll *)scheduler_get_data_for_core(SOF_SCHEDULE_LL_TIMER, + core); sys_mutex_unlock(&sch->lock); } diff --git a/uuid-registry.txt b/uuid-registry.txt index dc5cf5e15297..b4c27f1c895d 100644 --- a/uuid-registry.txt +++ b/uuid-registry.txt @@ -146,6 +146,7 @@ d7f6712d-131c-45a7-82ed6aa9dc2291ea pm_runtime 9302adf5-88be-4234-a0a7dca538ef81f4 sai 3dee06de-f25a-4e10-ae1fabc9573873ea schedule 70d223ef-2b91-4aac-b444d89a0db2793a sdma +bdcb1461-34f5-4047-b9cc70fdf8dfb234 sec_core_init 55a88ed5-3d18-46ca-88f10ee6eae9930f selector 32fe92c1-1e17-4fc2-9758c7f3542e980a selector4 cf90d851-68a2-4987-a2de85aed0c8531c sgen_mt8186 diff --git a/zephyr/schedule.c b/zephyr/schedule.c index 94623b2ed5db..403769128173 100644 --- a/zephyr/schedule.c +++ b/zephyr/schedule.c @@ -22,11 +22,21 @@ static APP_TASK_BSS struct schedulers *_schedulers[CONFIG_CORE_COUNT]; */ struct schedulers **arch_schedulers_get(void) { - if (k_is_user_context()) { - printk("FIXME: using core0 scheduler\n"); - return _schedulers; - } - return _schedulers + cpu_get_id(); } EXPORT_SYMBOL(arch_schedulers_get); + +#if CONFIG_SOF_USERSPACE_LL +/** + * Retrieves registered schedulers for a specific core. + * @param core Core ID to get schedulers for. + * @return List of registered schedulers for the specified core. + * + * Safe to call from user-space context — does not use cpu_get_id(). + */ +struct schedulers **arch_schedulers_get_for_core(int core) +{ + return _schedulers + core; +} +EXPORT_SYMBOL(arch_schedulers_get_for_core); +#endif From 3908f2cd5f0b2e46208ce0650096f0a6da007dd7 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Mon, 13 Apr 2026 16:51:05 +0300 Subject: [PATCH 090/106] ipc: ipc-helper: trace context not used in user-space No need to copy the trace context object to buffer object, when audio pipelines are running in user-space. Signed-off-by: Kai Vehmanen --- src/ipc/ipc-helper.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ipc/ipc-helper.c b/src/ipc/ipc-helper.c index 44465bbfc054..6034356553c7 100644 --- a/src/ipc/ipc-helper.c +++ b/src/ipc/ipc-helper.c @@ -86,8 +86,10 @@ __cold struct comp_buffer *buffer_new(struct k_heap *heap, const struct sof_ipc_ buffer->stream.runtime_stream_params.pipeline_id = desc->comp.pipeline_id; buffer->core = desc->comp.core; +#if !defined(CONFIG_SOF_USERSPACE_LL) memcpy_s(&buffer->tctx, sizeof(struct tr_ctx), &buffer_tr, sizeof(struct tr_ctx)); +#endif } return buffer; From 0d6ce0c28091c45799275ad542f0f5ec8ce5f6dd Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Mon, 13 Apr 2026 16:52:34 +0300 Subject: [PATCH 091/106] ipc: ipc4: use the core number from IPC config To make ipc4_create_buffer() usable from user-space, do not call cpu_get_id() but instead rely on "src->ipc_config.core". If code is run in kernel space, check the core matching current active core, but skip the check if run in user-space (as check is privileged). Signed-off-by: Kai Vehmanen --- src/ipc/ipc4/helper.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ipc/ipc4/helper.c b/src/ipc/ipc4/helper.c index b766e6434f0d..2a305bbd25f9 100644 --- a/src/ipc/ipc4/helper.c +++ b/src/ipc/ipc4/helper.c @@ -574,7 +574,10 @@ __cold static struct comp_buffer *ipc4_create_buffer(struct comp_dev *src, bool ipc_buf.size = buf_size; ipc_buf.comp.id = IPC4_COMP_ID(src_queue, dst_queue); ipc_buf.comp.pipeline_id = src->ipc_config.pipeline_id; - ipc_buf.comp.core = cpu_get_id(); + + assert(IS_ENABLED(CONFIG_SOF_USERSPACE_LL) || src->ipc_config.core == cpu_get_id()); + ipc_buf.comp.core = src->ipc_config.core; + return buffer_new(heap, &ipc_buf, is_shared); } From d3220318078a8127ca123f93b84c009c5284a931 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Mon, 13 Apr 2026 17:04:02 +0300 Subject: [PATCH 092/106] ipc: ipc4: helper: enable limited ipc_comp_connect() in user-space Make ipc_comp_connect() safe to run in user-space with limited functionality. The core id queries are privileged, so limit connections to cases where pipeline is running on 0. Signed-off-by: Kai Vehmanen --- src/ipc/ipc4/helper.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/ipc/ipc4/helper.c b/src/ipc/ipc4/helper.c index 2a305bbd25f9..dc3bfee4d00a 100644 --- a/src/ipc/ipc4/helper.c +++ b/src/ipc/ipc4/helper.c @@ -663,8 +663,8 @@ static int ll_wait_finished_on_core(struct comp_dev *dev) #else #if CONFIG_SOF_USERSPACE_LL -#define ll_block(cross_core_bind, flags) zephyr_ll_lock_sched(cpu_get_id()) -#define ll_unblock(cross_core_bind, flags) zephyr_ll_unlock_sched(cpu_get_id()) +#define ll_block(cross_core_bind, flags) zephyr_ll_lock_sched(0) +#define ll_unblock(cross_core_bind, flags) zephyr_ll_unlock_sched(0) #else #define ll_block(cross_core_bind, flags) irq_local_disable(flags) #define ll_unblock(cross_core_bind, flags) irq_local_enable(flags) @@ -870,6 +870,12 @@ __cold int ipc_comp_connect(struct ipc *ipc, ipc_pipe_comp_connect *_connect) */ ll_block(cross_core_bind, flags); + /* + * TODO: for multicore user support, comp_bufffer_connect() + * needs to be converted to a syscall. for now, limit to core0 + */ + assert(IS_ENABLED(CONFIG_SOF_USERSPACE_LL) || source->ipc_config.core == cpu_get_id()); + if (cross_core_bind) { #if CONFIG_CROSS_CORE_STREAM /* Make sure LL has finished on both cores */ From 8c7ff96c53b8a23913c68406416cf2fbd38b1292 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Fri, 10 Apr 2026 18:30:50 +0300 Subject: [PATCH 093/106] WIP: ipc: ipc4: route MOD_CONFIG_GET/SET to user-space handler Move handling of SOF_IPC4_MOD_CONFIG_GET/GET to user-space IPC thread when SOF is built with CONFIG_SOF_USERSPACE_LL. Handling these IPC messages require calls to module interface get/set_attribute() methods, which must be run in user-space. Signed-off-by: Kai Vehmanen --- src/include/ipc4/handler.h | 10 ++++++ src/include/sof/ipc/common.h | 2 ++ src/ipc/ipc-common.c | 67 ++++++++++++++++++++++++++---------- src/ipc/ipc4/handler-user.c | 48 ++++++++++++++++++++++++-- 4 files changed, 107 insertions(+), 20 deletions(-) diff --git a/src/include/ipc4/handler.h b/src/include/ipc4/handler.h index b25cb98e9427..fa8259d02e8b 100644 --- a/src/include/ipc4/handler.h +++ b/src/include/ipc4/handler.h @@ -16,6 +16,16 @@ struct ipc4_message_request; */ int ipc4_user_process_module_message(struct ipc4_message_request *ipc4, struct ipc_msg *reply); +/** + * @brief Process MOD_CONFIG_GET or MOD_CONFIG_SET in any execution context. + * @param[in] ipc4 IPC4 message request. + * @param[in] set true for CONFIG_SET, false for CONFIG_GET. + * @param[out] reply_ext Receives extension value for CONFIG_GET (may be NULL). + * @return IPC4 status code (0 on success). + */ +int ipc4_process_module_config(struct ipc4_message_request *ipc4, + bool set, uint32_t *reply_ext); + /** * \brief Processes IPC4 userspace global message. * @param[in] ipc4 IPC4 message request. diff --git a/src/include/sof/ipc/common.h b/src/include/sof/ipc/common.h index 7fa6e1977a26..80b71f12e27a 100644 --- a/src/include/sof/ipc/common.h +++ b/src/include/sof/ipc/common.h @@ -64,6 +64,8 @@ struct ipc_user { uint32_t ipc_msg_ext; /** @brief Result code from user thread processing */ int result; + /** @brief Reply extension word from user thread (e.g. CONFIG_GET result) */ + uint32_t reply_ext; struct ipc *ipc; struct k_thread *audio_thread; }; diff --git a/src/ipc/ipc-common.c b/src/ipc/ipc-common.c index 2fe89fd77caf..78b1d0dd8554 100644 --- a/src/ipc/ipc-common.c +++ b/src/ipc/ipc-common.c @@ -45,6 +45,8 @@ #include #include #include +#include +#include #endif #include @@ -406,24 +408,53 @@ static void ipc_user_thread_fn(void *p1, void *p2, void *p3) msg.primary.dat = ipc_user->ipc_msg_pri; msg.extension.dat = ipc_user->ipc_msg_ext; - switch (msg.primary.r.type) { - case SOF_IPC4_GLB_CREATE_PIPELINE: - ipc_user->result = ipc_pipeline_new(ipc_user->ipc, - (ipc_pipe_new *)&msg); - break; - case SOF_IPC4_GLB_DELETE_PIPELINE: { - struct ipc4_pipeline_delete *pipe = - (struct ipc4_pipeline_delete *)&msg; - - ipc_user->result = ipc_pipeline_free( - ipc_user->ipc, pipe->primary.r.instance_id); - break; - } - default: - LOG_ERR("IPC user: unsupported cmd type %d", - msg.primary.r.type); - ipc_user->result = -EINVAL; - break; + ipc_user->reply_ext = 0; + + if (msg.primary.r.msg_tgt == + SOF_IPC4_MESSAGE_TARGET_MODULE_MSG) { + /* Module message dispatch */ + switch (msg.primary.r.type) { + case SOF_IPC4_MOD_CONFIG_GET: + ipc_user->result = + ipc4_process_module_config( + &msg, false, + &ipc_user->reply_ext); + break; + case SOF_IPC4_MOD_CONFIG_SET: + ipc_user->result = + ipc4_process_module_config( + &msg, true, NULL); + break; + default: + LOG_ERR("IPC user: unsupported module cmd type %d", + msg.primary.r.type); + ipc_user->result = -EINVAL; + break; + } + } else { + /* Global message dispatch */ + switch (msg.primary.r.type) { + case SOF_IPC4_GLB_CREATE_PIPELINE: + ipc_user->result = + ipc_pipeline_new(ipc_user->ipc, + (ipc_pipe_new *)&msg); + break; + case SOF_IPC4_GLB_DELETE_PIPELINE: { + struct ipc4_pipeline_delete *pipe = + (struct ipc4_pipeline_delete *)&msg; + + ipc_user->result = + ipc_pipeline_free( + ipc_user->ipc, + pipe->primary.r.instance_id); + break; + } + default: + LOG_ERR("IPC user: unsupported glb cmd type %d", + msg.primary.r.type); + ipc_user->result = -EINVAL; + break; + } } /* Signal completion — kernel side will finish IPC */ diff --git a/src/ipc/ipc4/handler-user.c b/src/ipc/ipc4/handler-user.c index 78fa3b6c2633..f311331fddf2 100644 --- a/src/ipc/ipc4/handler-user.c +++ b/src/ipc/ipc4/handler-user.c @@ -803,7 +803,22 @@ __cold static int ipc4_unbind_module_instance(struct ipc4_message_request *ipc4) return ipc_comp_disconnect(ipc, (ipc_pipe_comp_connect *)&bu); } -static int ipc4_set_get_config_module_instance(struct ipc4_message_request *ipc4, bool set) +/** + * @brief Process MOD_CONFIG_GET or MOD_CONFIG_SET in any execution context. + * + * Looks up the target component by module_id:instance_id, verifies the + * driver supports get_attribute/set_attribute, and dispatches the + * operation. For GET, the retrieved value is written to @p reply_ext. + * + * Callable from both the IPC kernel task and the IPC user thread. + * + * @param ipc4 Pointer to the IPC4 message request (primary + extension) + * @param set true for CONFIG_SET, false for CONFIG_GET + * @param reply_ext Output: receives the extension value for CONFIG_GET (may be NULL for SET) + * @return IPC4 status code (0 on success) + */ +__cold int ipc4_process_module_config(struct ipc4_message_request *ipc4, + bool set, uint32_t *reply_ext) { struct ipc4_module_config *config = (struct ipc4_module_config *)ipc4; int (*function)(struct comp_dev *dev, uint32_t type, void *value); @@ -849,8 +864,21 @@ static int ipc4_set_get_config_module_instance(struct ipc4_message_request *ipc4 ret = IPC4_INVALID_CONFIG_PARAM_ID; } + if (!set && reply_ext) + *reply_ext = config->extension.dat; + + return ret; +} + +static int ipc4_set_get_config_module_instance(struct ipc4_message_request *ipc4, bool set) +{ + uint32_t reply_ext; + int ret; + + ret = ipc4_process_module_config(ipc4, set, &reply_ext); + if (!set) - msg_reply->extension = config->extension.dat; + msg_reply->extension = reply_ext; return ret; } @@ -1258,10 +1286,26 @@ __cold int ipc4_user_process_module_message(struct ipc4_message_request *ipc4, ret = ipc4_init_module_instance(ipc4); break; case SOF_IPC4_MOD_CONFIG_GET: +#ifdef CONFIG_SOF_USERSPACE_LL + /* Forward to user thread for privilege-separated execution */ + ret = ipc_user_forward_cmd(ipc4); + if (!ret) { + struct ipc *ipc = ipc_get(); + struct ipc_user *pdata = ipc->ipc_user_pdata; + + msg_reply->extension = pdata->reply_ext; + } +#else ret = ipc4_set_get_config_module_instance(ipc4, false); +#endif break; case SOF_IPC4_MOD_CONFIG_SET: +#ifdef CONFIG_SOF_USERSPACE_LL + /* Forward to user thread for privilege-separated execution */ + ret = ipc_user_forward_cmd(ipc4); +#else ret = ipc4_set_get_config_module_instance(ipc4, true); +#endif break; case SOF_IPC4_MOD_LARGE_CONFIG_GET: ret = ipc4_get_large_config_module_instance(ipc4); From d6479b27f8ab5ff52d6dd2f872c5b55b4bfe50be Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Fri, 10 Apr 2026 18:28:53 +0300 Subject: [PATCH 094/106] WIP: ipc: ipc4: route MOD_BIND/UNBIND to user-space handler Move handling of SOF_IPC4_MOD_BIND/UNBIND to user-space IPC thread when SOF is built with CONFIG_SOF_USERSPACE_LL. Handling these IPC messages require calls to module interface bind/unbind() methods, which must be run in user-space. Signed-off-by: Kai Vehmanen --- src/ipc/ipc-common.c | 22 +++++++++++++++++++--- src/ipc/ipc4/handler-user.c | 8 ++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/ipc/ipc-common.c b/src/ipc/ipc-common.c index 78b1d0dd8554..828f296605df 100644 --- a/src/ipc/ipc-common.c +++ b/src/ipc/ipc-common.c @@ -410,8 +410,7 @@ static void ipc_user_thread_fn(void *p1, void *p2, void *p3) ipc_user->reply_ext = 0; - if (msg.primary.r.msg_tgt == - SOF_IPC4_MESSAGE_TARGET_MODULE_MSG) { + if (msg.primary.r.msg_tgt == SOF_IPC4_MESSAGE_TARGET_MODULE_MSG) { /* Module message dispatch */ switch (msg.primary.r.type) { case SOF_IPC4_MOD_CONFIG_GET: @@ -425,6 +424,24 @@ static void ipc_user_thread_fn(void *p1, void *p2, void *p3) ipc4_process_module_config( &msg, true, NULL); break; + case SOF_IPC4_MOD_BIND: { + struct ipc4_module_bind_unbind bu; + + memcpy_s(&bu, sizeof(bu), &msg, sizeof(msg)); + ipc_user->result = ipc_comp_connect( + ipc_user->ipc, + (ipc_pipe_comp_connect *)&bu); + break; + } + case SOF_IPC4_MOD_UNBIND: { + struct ipc4_module_bind_unbind bu; + + memcpy_s(&bu, sizeof(bu), &msg, sizeof(msg)); + ipc_user->result = ipc_comp_disconnect( + ipc_user->ipc, + (ipc_pipe_comp_connect *)&bu); + break; + } default: LOG_ERR("IPC user: unsupported module cmd type %d", msg.primary.r.type); @@ -442,7 +459,6 @@ static void ipc_user_thread_fn(void *p1, void *p2, void *p3) case SOF_IPC4_GLB_DELETE_PIPELINE: { struct ipc4_pipeline_delete *pipe = (struct ipc4_pipeline_delete *)&msg; - ipc_user->result = ipc_pipeline_free( ipc_user->ipc, diff --git a/src/ipc/ipc4/handler-user.c b/src/ipc/ipc4/handler-user.c index f311331fddf2..4a9c4a901a8f 100644 --- a/src/ipc/ipc4/handler-user.c +++ b/src/ipc/ipc4/handler-user.c @@ -1314,10 +1314,18 @@ __cold int ipc4_user_process_module_message(struct ipc4_message_request *ipc4, ret = ipc4_set_large_config_module_instance(ipc4); break; case SOF_IPC4_MOD_BIND: +#ifdef CONFIG_SOF_USERSPACE_LL + ret = ipc_user_forward_cmd(ipc4); +#else ret = ipc4_bind_module_instance(ipc4); +#endif break; case SOF_IPC4_MOD_UNBIND: +#ifdef CONFIG_SOF_USERSPACE_LL + ret = ipc_user_forward_cmd(ipc4); +#else ret = ipc4_unbind_module_instance(ipc4); +#endif break; case SOF_IPC4_MOD_DELETE_INSTANCE: ret = ipc4_delete_module_instance(ipc4); From 828cdb374d270c6bb9d46b41aff7523080dbd3a4 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Wed, 15 Apr 2026 13:54:30 +0300 Subject: [PATCH 095/106] WIP: ipc: ipc4: route MOD_INIT_INSTANCE to user-space handler Move handling of SOF_IPC4_IPC4_MOD_INIT_INSTANCE partially to user-space IPC thread when SOF is built with CONFIG_SOF_USERSPACE_LL. Module init involves multiple privileged actions, so part of module init handling is still done in kernel space, but the module specific initialization is run in user-space. Signed-off-by: Kai Vehmanen --- src/include/sof/audio/buffer.h | 1 + src/include/sof/ipc/common.h | 12 ++++ src/include/sof/ipc/topology.h | 7 +++ src/ipc/ipc-common.c | 67 ++++++++++++++++++++++ src/ipc/ipc4/handler-user.c | 59 +++++++++++++++++++ src/ipc/ipc4/helper.c | 102 ++++++++++++++++++++++++++++++++- zephyr/lib/userspace_helper.c | 37 ++++++++++++ 7 files changed, 283 insertions(+), 2 deletions(-) diff --git a/src/include/sof/audio/buffer.h b/src/include/sof/audio/buffer.h index 39926331ea78..71adf4ea0436 100644 --- a/src/include/sof/audio/buffer.h +++ b/src/include/sof/audio/buffer.h @@ -34,6 +34,7 @@ struct comp_dev; struct k_heap; +struct buffer_cb_transact; /** \name Trace macros * @{ diff --git a/src/include/sof/ipc/common.h b/src/include/sof/ipc/common.h index 80b71f12e27a..0218b72519a0 100644 --- a/src/include/sof/ipc/common.h +++ b/src/include/sof/ipc/common.h @@ -22,6 +22,7 @@ #include #include +struct comp_driver; struct dma_sg_elem_array; struct ipc_msg; struct ipc4_message_request; @@ -68,6 +69,17 @@ struct ipc_user { uint32_t reply_ext; struct ipc *ipc; struct k_thread *audio_thread; + /** @brief Original kernel driver pointer for restoring dev->drv after create */ + const struct comp_driver *init_drv; + /** + * @brief User-accessible copy of comp_driver + tr_ctx for create(). + * + * The comp_driver and tr_ctx structs reside in kernel memory + * (.rodata/.data) which is not user-readable. The kernel handler + * copies them here before forwarding to the user thread. + * Size verified by BUILD_ASSERT in handler-user.c. + */ + uint8_t init_drv_data[160] __aligned(4); }; struct ipc { diff --git a/src/include/sof/ipc/topology.h b/src/include/sof/ipc/topology.h index 3503c7a407d8..590424ee8be9 100644 --- a/src/include/sof/ipc/topology.h +++ b/src/include/sof/ipc/topology.h @@ -49,6 +49,13 @@ typedef uint32_t ipc_comp; struct ipc_comp_dev; const struct comp_driver *ipc4_get_comp_drv(uint32_t module_id); struct comp_dev *ipc4_get_comp_dev(uint32_t comp_id); +int ipc4_add_comp_dev(struct comp_dev *dev); +#ifdef CONFIG_SOF_USERSPACE_LL +struct ipc4_message_request; +struct comp_driver; +struct comp_dev *comp_new_ipc4_user(struct ipc4_message_request *ipc4, + const struct comp_driver *drv); +#endif int ipc4_chain_manager_create(struct ipc4_chain_dma *cdma); int ipc4_chain_dma_state(struct comp_dev *dev, struct ipc4_chain_dma *cdma); int ipc4_create_chain_dma(struct ipc *ipc, struct ipc4_chain_dma *cdma); diff --git a/src/ipc/ipc-common.c b/src/ipc/ipc-common.c index 828f296605df..acdb89442f0f 100644 --- a/src/ipc/ipc-common.c +++ b/src/ipc/ipc-common.c @@ -442,6 +442,67 @@ static void ipc_user_thread_fn(void *p1, void *p2, void *p3) (ipc_pipe_comp_connect *)&bu); break; } + case SOF_IPC4_MOD_INIT_INSTANCE: { + /* User thread creates the component — + * drv->ops.create() runs in user-space so + * untrusted module code does not execute + * with kernel privileges. + * + * init_drv = original kernel pointer + * init_drv_data = user-accessible copy + */ + const struct comp_driver *orig_drv = + ipc_user->init_drv; + const struct comp_driver *drv_copy = + (const struct comp_driver *) + ipc_user->init_drv_data; + + ipc_user->init_drv = NULL; + if (!orig_drv) { + ipc_user->result = + IPC4_MOD_NOT_INITIALIZED; + break; + } + + struct comp_dev *dev = + comp_new_ipc4_user(&msg, drv_copy); + + if (!dev) { + ipc_user->result = + IPC4_MOD_NOT_INITIALIZED; + break; + } + + /* Restore original kernel driver pointer. + * comp_init() set dev->drv to the copy; + * runtime code expects the canonical + * kernel address. + */ + dev->drv = orig_drv; + + ipc_user->result = + ipc4_add_comp_dev(dev); + if (ipc_user->result != IPC4_SUCCESS) + break; + + comp_update_ibs_obs_cpc(dev); + ipc_user->result = 0; + break; + } + case SOF_IPC4_MOD_DELETE_INSTANCE: { + struct ipc4_module_delete_instance module; + + memcpy_s(&module, sizeof(module), &msg, sizeof(msg)); + uint32_t comp_id = IPC4_COMP_ID( + module.primary.r.module_id, + module.primary.r.instance_id); + ipc_user->result = ipc_comp_free( + ipc_user->ipc, comp_id); + if (ipc_user->result < 0) + ipc_user->result = + IPC4_INVALID_RESOURCE_ID; + break; + } default: LOG_ERR("IPC user: unsupported module cmd type %d", msg.primary.r.type); @@ -533,6 +594,12 @@ __cold int ipc_user_init(void) 0, NULL, NULL, cpu_get_id(), 0); ipc_user->audio_thread = scheduler_init_context(task); + /* Grant ipc_user thread permission on the audio thread object. + * Needed so user-space dai_common_new() can call + * k_thread_access_grant(audio_thread, dai_mutex) from user context. + */ + k_thread_access_grant(&ipc_user_thread, ipc_user->audio_thread); + /* Wait for user thread startup — consumes the initial k_sem_give from thread */ k_sem_take(ipc->ipc_user_pdata->sem, K_FOREVER); diff --git a/src/ipc/ipc4/handler-user.c b/src/ipc/ipc4/handler-user.c index 4a9c4a901a8f..fd8a2d0b2889 100644 --- a/src/ipc/ipc4/handler-user.c +++ b/src/ipc/ipc4/handler-user.c @@ -1283,7 +1283,62 @@ __cold int ipc4_user_process_module_message(struct ipc4_message_request *ipc4, switch (type) { case SOF_IPC4_MOD_INIT_INSTANCE: +#ifdef CONFIG_SOF_USERSPACE_LL + { + /* User-space init: kernel does driver lookup only (requires + * access to IMR manifest and driver list in kernel memory). + * Component creation (drv->ops.create) runs in user thread + * so untrusted module code does not execute in kernel context. + * Cross-core creation stays fully in kernel. + */ + struct ipc4_module_init_instance mi; + + BUILD_ASSERT(sizeof(struct comp_driver) + sizeof(struct tr_ctx) <= + sizeof(((struct ipc_user *)0)->init_drv_data), + "ipc_user.init_drv_data too small for driver copy"); + + memcpy_s(&mi, sizeof(mi), ipc4, sizeof(*ipc4)); + if (!cpu_is_me(mi.extension.r.core_id)) { + ret = ipc4_init_module_instance(ipc4); + } else { + struct ipc *ipc = ipc_get(); + uint32_t comp_id = IPC4_COMP_ID(mi.primary.r.module_id, + mi.primary.r.instance_id); + const struct comp_driver *drv = ipc4_get_comp_drv( + IPC4_MOD_ID(comp_id)); + + if (!drv) { + ret = IPC4_MOD_NOT_INITIALIZED; + } else { + struct ipc_user *pdata = ipc->ipc_user_pdata; + + /* Copy comp_driver and tr_ctx into + * user-accessible ipc_user buffer — + * originals are in kernel .rodata/.data + * and not readable from user mode. + */ + struct comp_driver *drv_copy = + (struct comp_driver *)pdata->init_drv_data; + struct tr_ctx *tctx_copy = + (struct tr_ctx *)(pdata->init_drv_data + + sizeof(struct comp_driver)); + + memcpy_s(drv_copy, sizeof(*drv_copy), + drv, sizeof(*drv)); + if (drv->tctx) { + memcpy_s(tctx_copy, sizeof(*tctx_copy), + drv->tctx, sizeof(*drv->tctx)); + drv_copy->tctx = tctx_copy; + } + + pdata->init_drv = drv; + ret = ipc_user_forward_cmd(ipc4); + } + } + } +#else ret = ipc4_init_module_instance(ipc4); +#endif break; case SOF_IPC4_MOD_CONFIG_GET: #ifdef CONFIG_SOF_USERSPACE_LL @@ -1328,7 +1383,11 @@ __cold int ipc4_user_process_module_message(struct ipc4_message_request *ipc4, #endif break; case SOF_IPC4_MOD_DELETE_INSTANCE: +#ifdef CONFIG_SOF_USERSPACE_LL + ret = ipc_user_forward_cmd(ipc4); +#else ret = ipc4_delete_module_instance(ipc4); +#endif break; case SOF_IPC4_MOD_ENTER_MODULE_RESTORE: case SOF_IPC4_MOD_EXIT_MODULE_RESTORE: diff --git a/src/ipc/ipc4/helper.c b/src/ipc/ipc4/helper.c index dc3bfee4d00a..5ef304b0d083 100644 --- a/src/ipc/ipc4/helper.c +++ b/src/ipc/ipc4/helper.c @@ -63,7 +63,6 @@ LOG_MODULE_DECLARE(ipc, CONFIG_SOF_LOG_LEVEL); extern struct tr_ctx comp_tr; static const struct comp_driver *ipc4_get_drv(const void *uuid); -static int ipc4_add_comp_dev(struct comp_dev *dev); void ipc_build_stream_posn(struct sof_ipc_stream_posn *posn, uint32_t type, uint32_t id) @@ -208,6 +207,105 @@ __cold struct comp_dev *comp_new_ipc4(struct ipc4_module_init_instance *module_i return dev; } +#ifdef CONFIG_SOF_USERSPACE_LL +/** + * comp_new_ipc4_user - Create component in user-space IPC thread context. + * + * Called from the user-space IPC thread. Receives a pre-resolved driver + * pointer from the kernel handler. Performs IPC4 message parsing, HOSTBOX + * data read, and calls drv->ops.create() in user-space context. + * + * @param ipc4 IPC4 message request (reconstructed from ipc_user pri/ext words) + * @param drv Component driver resolved by kernel via ipc4_get_comp_drv() + * @return Created component device, or NULL on failure + */ +__cold struct comp_dev *comp_new_ipc4_user(struct ipc4_message_request *ipc4, + const struct comp_driver *drv) +{ + struct ipc4_module_init_instance module_init; + struct comp_ipc_config ipc_config; + struct comp_dev *dev; + uint32_t comp_id; + char *data; + int ret; + + assert_can_be_cold(); + + ret = memcpy_s(&module_init, sizeof(module_init), ipc4, sizeof(*ipc4)); + if (ret < 0) + return NULL; + + comp_id = IPC4_COMP_ID(module_init.primary.r.module_id, + module_init.primary.r.instance_id); + + if (ipc4_get_comp_dev(comp_id)) { + tr_err(&ipc_tr, "comp 0x%x exists", comp_id); + return NULL; + } + + if (module_init.extension.r.core_id >= CONFIG_CORE_COUNT) { + tr_err(&ipc_tr, "ipc: comp->core = %u", + (uint32_t)module_init.extension.r.core_id); + return NULL; + } + + memset(&ipc_config, 0, sizeof(ipc_config)); + ipc_config.id = comp_id; + ipc_config.pipeline_id = module_init.extension.r.ppl_instance_id; + ipc_config.core = module_init.extension.r.core_id; + ipc_config.ipc_config_size = + module_init.extension.r.param_block_size * sizeof(uint32_t); + ipc_config.ipc_extended_init = module_init.extension.r.extended_init; + if (ipc_config.ipc_config_size > MAILBOX_HOSTBOX_SIZE) { + tr_err(&ipc_tr, + "IPC payload size %u too big for the message window", + ipc_config.ipc_config_size); + return NULL; + } +#ifdef CONFIG_DCACHE_LINE_SIZE + if (!IS_ENABLED(CONFIG_LIBRARY)) + sys_cache_data_invd_range( + (__sparse_force void __sparse_cache *)MAILBOX_HOSTBOX_BASE, + ALIGN_UP(ipc_config.ipc_config_size, + CONFIG_DCACHE_LINE_SIZE)); +#endif + data = ipc4_get_comp_new_data(); + +#if CONFIG_ZEPHYR_DP_SCHEDULER + if (module_init.extension.r.proc_domain) + ipc_config.proc_domain = COMP_PROCESSING_DOMAIN_DP; + else + ipc_config.proc_domain = COMP_PROCESSING_DOMAIN_LL; +#else + if (module_init.extension.r.proc_domain) { + tr_err(&ipc_tr, + "ipc: DP scheduling is disabled, cannot create comp 0x%x", + comp_id); + return NULL; + } + ipc_config.proc_domain = COMP_PROCESSING_DOMAIN_LL; +#endif + + if (drv->type == SOF_COMP_MODULE_ADAPTER) { + const struct ipc_config_process spec = { + .data = (const unsigned char *)data, + .size = ipc_config.ipc_config_size, + }; + + dev = drv->ops.create(drv, &ipc_config, (const void *)&spec); + } else { + dev = drv->ops.create(drv, &ipc_config, (const void *)data); + } + if (!dev) + return NULL; + + list_init(&dev->bsource_list); + list_init(&dev->bsink_list); + + return dev; +} +#endif /* CONFIG_SOF_USERSPACE_LL */ + /* Called from ipc4_set_pipeline_state(), so cannot be cold */ struct ipc_comp_dev *ipc_get_comp_by_ppl_id(struct ipc *ipc, uint16_t type, uint32_t ppl_id, @@ -1357,7 +1455,7 @@ struct comp_dev *ipc4_get_comp_dev(uint32_t comp_id) } EXPORT_SYMBOL(ipc4_get_comp_dev); -__cold static int ipc4_add_comp_dev(struct comp_dev *dev) +__cold int ipc4_add_comp_dev(struct comp_dev *dev) { struct ipc *ipc = ipc_get(); struct ipc_comp_dev *icd; diff --git a/zephyr/lib/userspace_helper.c b/zephyr/lib/userspace_helper.c index 3ce8e81f72b8..b8b472b11f7d 100644 --- a/zephyr/lib/userspace_helper.c +++ b/zephyr/lib/userspace_helper.c @@ -151,6 +151,43 @@ int user_access_to_mailbox(struct k_mem_domain *domain, k_tid_t thread_id) if (ret < 0) return ret; +#if defined(CONFIG_SOF_USERSPACE_LL) && defined(CONFIG_IPC_MAJOR_4) + /* HOSTBOX partitions for IPC4 module init parameter block reads. + * comp_new_ipc4() accesses MAILBOX_HOSTBOX_BASE directly to get + * the module configuration data sent by the host. + */ + { + struct k_mem_partition hostbox_partition; + + /* Uncached HOSTBOX partition */ + hostbox_partition.start = + (uintptr_t)sys_cache_uncached_ptr_get( + (void __sparse_cache *)MAILBOX_HOSTBOX_BASE); + hostbox_partition.size = ALIGN_UP(MAILBOX_HOSTBOX_SIZE, + CONFIG_MMU_PAGE_SIZE); + hostbox_partition.attr = K_MEM_PARTITION_P_RO_U_RO; + + ret = k_mem_domain_add_partition(domain, &hostbox_partition); + if (ret < 0) + return ret; + + /* Cached HOSTBOX partition for cache invalidation path. + * sys_cache_data_invd_range() syscall verification requires + * write access to the region, so use RW instead of RO. + */ + hostbox_partition.start = + (uintptr_t)sys_cache_cached_ptr_get( + (void *)MAILBOX_HOSTBOX_BASE); + hostbox_partition.size = ALIGN_UP(MAILBOX_HOSTBOX_SIZE, + CONFIG_MMU_PAGE_SIZE); + hostbox_partition.attr = K_MEM_PARTITION_P_RW_U_RW; + + ret = k_mem_domain_add_partition(domain, &hostbox_partition); + if (ret < 0) + return ret; + } +#endif /* CONFIG_IPC_MAJOR_4 */ + #ifndef CONFIG_IPC_MAJOR_4 /* * Next mailbox_stream (not available in IPC4). Stream access is cached, From a1dd5571fd27e2b1f529c55a81550cdc3cba9161 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 16 Apr 2026 15:05:00 +0300 Subject: [PATCH 096/106] WIP: ipc: ipc4: route GLB_SET_PIPELINE_STATE to user-space handler Move handling of SOF_IPC4_GLB_SET_PIPELINE_STATE to user-space IPC thread when SOF is built with CONFIG_SOF_USERSPACE_LL. The IPC compound message helpers in IPC common.h are turned into system calls, allowing the user-space IPC thread to synchronize with kernel IPC thread that still does the low-level IPC messaging. Signed-off-by: Kai Vehmanen --- src/include/ipc4/handler.h | 40 ++++++++++++++++++------ src/include/sof/ipc/common.h | 19 ++++++++---- src/ipc/ipc-common.c | 4 +++ src/ipc/ipc4/handler-kernel.c | 58 ++++++++++++++++++++++++++++++++--- src/ipc/ipc4/handler-user.c | 12 ++++++-- zephyr/CMakeLists.txt | 1 + 6 files changed, 112 insertions(+), 22 deletions(-) diff --git a/src/include/ipc4/handler.h b/src/include/ipc4/handler.h index fa8259d02e8b..dcde958a2442 100644 --- a/src/include/ipc4/handler.h +++ b/src/include/ipc4/handler.h @@ -35,30 +35,50 @@ int ipc4_process_module_config(struct ipc4_message_request *ipc4, int ipc4_user_process_glb_message(struct ipc4_message_request *ipc4, struct ipc_msg *reply); /** - * \brief Increment the IPC compound message pre-start counter. + * \brief Process SET_PIPELINE_STATE IPC4 message (prepare + trigger phases). + * @param[in] ipc4 IPC4 message request. + * @return 0 on success, IPC4 error code otherwise. + */ +int ipc4_set_pipeline_state(struct ipc4_message_request *ipc4); + +/** + * \brief Complete the IPC compound message. * @param[in] msg_id IPC message ID. + * @param[in] error Error code of the IPC command. */ -void ipc_compound_pre_start(int msg_id); +void ipc_compound_msg_done(uint32_t msg_id, int error); +#if defined(__ZEPHYR__) && defined(CONFIG_SOF_USERSPACE_LL) /** - * \brief Decrement the IPC compound message pre-start counter on return value status. + * \brief Increment the IPC compound message pre-start counter. * @param[in] msg_id IPC message ID. - * @param[in] ret Return value of the IPC command. - * @param[in] delayed True if the reply is delayed. */ -void ipc_compound_post_start(uint32_t msg_id, int ret, bool delayed); +__syscall void ipc_compound_pre_start(int msg_id); /** - * \brief Complete the IPC compound message. + * \brief Decrement the IPC compound message pre-start counter on return value status. * @param[in] msg_id IPC message ID. - * @param[in] error Error code of the IPC command. + * @param[in] ret Return value of the IPC command. + * @param[in] delayed True if the reply is delayed. */ -void ipc_compound_msg_done(uint32_t msg_id, int error); +__syscall void ipc_compound_post_start(uint32_t msg_id, int ret, bool delayed); /** * \brief Wait for the IPC compound message to complete. * @return 0 on success, error code otherwise on timeout. */ -int ipc_wait_for_compound_msg(void); +__syscall int ipc_wait_for_compound_msg(void); +#else +void z_impl_ipc_compound_pre_start(int msg_id); +#define ipc_compound_pre_start z_impl_ipc_compound_pre_start +void z_impl_ipc_compound_post_start(uint32_t msg_id, int ret, bool delayed); +#define ipc_compound_post_start z_impl_ipc_compound_post_start +int z_impl_ipc_wait_for_compound_msg(void); +#define ipc_wait_for_compound_msg z_impl_ipc_wait_for_compound_msg +#endif + +#if defined(__ZEPHYR__) && defined(CONFIG_SOF_USERSPACE_LL) +#include +#endif #endif /* __SOF_IPC4_HANDLER_H__ */ diff --git a/src/include/sof/ipc/common.h b/src/include/sof/ipc/common.h index 0218b72519a0..8322da545aca 100644 --- a/src/include/sof/ipc/common.h +++ b/src/include/sof/ipc/common.h @@ -223,6 +223,12 @@ int ipc4_user_process_module_message(struct ipc4_message_request *ipc4, struct i */ int ipc4_user_process_glb_message(struct ipc4_message_request *ipc4, struct ipc_msg *reply); +/* + * When CONFIG_SOF_USERSPACE_LL is enabled, compound message functions are + * declared as syscalls in ipc4/handler.h — do not re-declare here with + * external linkage as that conflicts with the static inline syscall wrappers. + */ +#if !(defined(__ZEPHYR__) && defined(CONFIG_SOF_USERSPACE_LL)) /** * \brief Increment the IPC compound message pre-start counter. * @param[in] msg_id IPC message ID. @@ -237,6 +243,13 @@ void ipc_compound_pre_start(int msg_id); */ void ipc_compound_post_start(uint32_t msg_id, int ret, bool delayed); +/** + * \brief Wait for the IPC compound message to complete. + * @return 0 on success, error code otherwise on timeout. + */ +int ipc_wait_for_compound_msg(void); +#endif /* !CONFIG_SOF_USERSPACE_LL */ + /** * \brief Complete the IPC compound message. * @param[in] msg_id IPC message ID. @@ -244,12 +257,6 @@ void ipc_compound_post_start(uint32_t msg_id, int ret, bool delayed); */ void ipc_compound_msg_done(uint32_t msg_id, int error); -/** - * \brief Wait for the IPC compound message to complete. - * @return 0 on success, error code otherwise on timeout. - */ -int ipc_wait_for_compound_msg(void); - /** * \brief create a IPC boot complete message. * @param[in] header header. diff --git a/src/ipc/ipc-common.c b/src/ipc/ipc-common.c index acdb89442f0f..7e9c445d7168 100644 --- a/src/ipc/ipc-common.c +++ b/src/ipc/ipc-common.c @@ -526,6 +526,10 @@ static void ipc_user_thread_fn(void *p1, void *p2, void *p3) pipe->primary.r.instance_id); break; } + case SOF_IPC4_GLB_SET_PIPELINE_STATE: + ipc_user->result = + ipc4_set_pipeline_state(&msg); + break; default: LOG_ERR("IPC user: unsupported glb cmd type %d", msg.primary.r.type); diff --git a/src/ipc/ipc4/handler-kernel.c b/src/ipc/ipc4/handler-kernel.c index a4613adc1fae..f8c74b2d4f32 100644 --- a/src/ipc/ipc4/handler-kernel.c +++ b/src/ipc/ipc4/handler-kernel.c @@ -139,7 +139,7 @@ __cold static bool is_any_ppl_active(void) return false; } -void ipc_compound_pre_start(int msg_id) +void z_impl_ipc_compound_pre_start(int msg_id) { /* ipc thread will wait for all scheduled tasks to be complete * Use a reference count to check status of these tasks. @@ -147,7 +147,23 @@ void ipc_compound_pre_start(int msg_id) atomic_add(&msg_data.delayed_reply, 1); } -void ipc_compound_post_start(uint32_t msg_id, int ret, bool delayed) +#ifdef CONFIG_USERSPACE +/** + * \brief Userspace verification wrapper for ipc_compound_pre_start(). + * + * Forwards the call to z_impl_ipc_compound_pre_start(). No pointer + * validation is needed as only primitive types are passed. + * + * @param[in] msg_id IPC message ID. + */ +void z_vrfy_ipc_compound_pre_start(int msg_id) +{ + z_impl_ipc_compound_pre_start(msg_id); +} +#include +#endif + +void z_impl_ipc_compound_post_start(uint32_t msg_id, int ret, bool delayed) { if (ret) { ipc_cmd_err(&ipc_tr, "failed to process msg %d status %d", msg_id, ret); @@ -160,6 +176,24 @@ void ipc_compound_post_start(uint32_t msg_id, int ret, bool delayed) atomic_sub(&msg_data.delayed_reply, 1); } +#ifdef CONFIG_USERSPACE +/** + * \brief Userspace verification wrapper for ipc_compound_post_start(). + * + * Forwards the call to z_impl_ipc_compound_post_start(). No pointer + * validation is needed as only primitive types are passed. + * + * @param[in] msg_id IPC message ID. + * @param[in] ret Return value of the IPC command. + * @param[in] delayed True if the reply is delayed. + */ +void z_vrfy_ipc_compound_post_start(uint32_t msg_id, int ret, bool delayed) +{ + z_impl_ipc_compound_post_start(msg_id, ret, delayed); +} +#include +#endif + void ipc_compound_msg_done(uint32_t msg_id, int error) { if (!atomic_read(&msg_data.delayed_reply)) { @@ -181,13 +215,13 @@ void ipc_compound_msg_done(uint32_t msg_id, int error) * be always IPC4_FAILURE. Therefore the compound messages handling is simplified. The pipeline * triggers will require an explicit scheduler call to get the components to desired state. */ -int ipc_wait_for_compound_msg(void) +int z_impl_ipc_wait_for_compound_msg(void) { atomic_set(&msg_data.delayed_reply, 0); return IPC4_SUCCESS; } #else -int ipc_wait_for_compound_msg(void) +int z_impl_ipc_wait_for_compound_msg(void) { int try_count = 30; @@ -203,6 +237,22 @@ int ipc_wait_for_compound_msg(void) return IPC4_SUCCESS; } + +#ifdef CONFIG_USERSPACE +/** + * \brief Userspace verification wrapper for ipc_wait_for_compound_msg(). + * + * Forwards the call to z_impl_ipc_wait_for_compound_msg(). No pointer + * validation is needed as no pointers are passed. + * + * @return IPC4_SUCCESS on success, IPC4_FAILURE on timeout. + */ +int z_vrfy_ipc_wait_for_compound_msg(void) +{ + return z_impl_ipc_wait_for_compound_msg(); +} +#include +#endif #endif #if CONFIG_LIBRARY_MANAGER diff --git a/src/ipc/ipc4/handler-user.c b/src/ipc/ipc4/handler-user.c index fd8a2d0b2889..7711ac53289d 100644 --- a/src/ipc/ipc4/handler-user.c +++ b/src/ipc/ipc4/handler-user.c @@ -423,8 +423,12 @@ __cold const struct ipc4_pipeline_set_state_data *ipc4_get_pipeline_data_wrapper return ipc4_get_pipeline_data(); } -/* Entry point for ipc4_pipeline_trigger(), therefore cannot be cold */ -static int ipc4_set_pipeline_state(struct ipc4_message_request *ipc4) +/** + * \brief Process SET_PIPELINE_STATE IPC4 message (prepare + trigger phases). + * @param[in] ipc4 IPC4 message request. + * @return 0 on success, IPC4 error code otherwise. + */ +int ipc4_set_pipeline_state(struct ipc4_message_request *ipc4) { const struct ipc4_pipeline_set_state_data *ppl_data; struct ipc4_pipeline_set_state state; @@ -685,7 +689,11 @@ int ipc4_user_process_glb_message(struct ipc4_message_request *ipc4, #endif break; case SOF_IPC4_GLB_SET_PIPELINE_STATE: +#ifdef CONFIG_SOF_USERSPACE_LL + ret = ipc_user_forward_cmd(ipc4); +#else ret = ipc4_set_pipeline_state(ipc4); +#endif break; case SOF_IPC4_GLB_GET_PIPELINE_STATE: diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index ee4091d52f48..e12d4b55a7af 100644 --- a/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -550,6 +550,7 @@ 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(${SOF_SRC_PATH}/include/sof/ipc/ipc_reply.h) +zephyr_syscall_header(${SOF_SRC_PATH}/include/ipc4/handler.h) zephyr_syscall_header(include/rtos/alloc.h) zephyr_library_sources(syscall/alloc.c) zephyr_syscall_header(${SOF_SRC_PATH}/include/sof/lib/dai-zephyr.h) From 92871485a17a2749a7298115b5dde2e3a2cd5fa6 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 16 Apr 2026 19:18:23 +0300 Subject: [PATCH 097/106] WIP: ipc: ipc4: route MOD_LARGE_CONFIG_GET/SET user-space Move handling of SOF_IPC4_MOD_LARGE_CONFIG_GET/SET to user-space IPC thread when SOF is built with CONFIG_SOF_USERSPACE_LL. Handling of these IPCs requires calling to module specific code via the module get/set_large_config() method, so this code needs to run in user-space context if audio pipelines are running in user-space. Signed-off-by: Kai Vehmanen --- src/include/ipc4/handler.h | 20 ++++ src/include/sof/ipc/common.h | 4 + src/ipc/ipc-common.c | 13 +++ src/ipc/ipc4/handler-user.c | 213 +++++++++++++++++++++++++++++++++++ 4 files changed, 250 insertions(+) diff --git a/src/include/ipc4/handler.h b/src/include/ipc4/handler.h index dcde958a2442..6073e4dc83c0 100644 --- a/src/include/ipc4/handler.h +++ b/src/include/ipc4/handler.h @@ -26,6 +26,26 @@ int ipc4_user_process_module_message(struct ipc4_message_request *ipc4, struct i int ipc4_process_module_config(struct ipc4_message_request *ipc4, bool set, uint32_t *reply_ext); +/** + * @brief Process MOD_LARGE_CONFIG_GET in any execution context. + * @param[in] ipc4 IPC4 message request. + * @param[out] reply_ext Receives extension value for reply. + * @param[out] reply_tx_size Receives TX data size for reply. + * @param[out] reply_tx_data Receives TX data pointer for reply. + * @return IPC4 status code (0 on success). + */ +int ipc4_process_large_config_get(struct ipc4_message_request *ipc4, + uint32_t *reply_ext, + uint32_t *reply_tx_size, + void **reply_tx_data); + +/** + * @brief Process MOD_LARGE_CONFIG_SET in any execution context. + * @param[in] ipc4 IPC4 message request. + * @return IPC4 status code (0 on success). + */ +int ipc4_process_large_config_set(struct ipc4_message_request *ipc4); + /** * \brief Processes IPC4 userspace global message. * @param[in] ipc4 IPC4 message request. diff --git a/src/include/sof/ipc/common.h b/src/include/sof/ipc/common.h index 8322da545aca..d7e76d3955a3 100644 --- a/src/include/sof/ipc/common.h +++ b/src/include/sof/ipc/common.h @@ -67,6 +67,10 @@ struct ipc_user { int result; /** @brief Reply extension word from user thread (e.g. CONFIG_GET result) */ uint32_t reply_ext; + /** @brief Reply TX data size from user thread (e.g. LARGE_CONFIG_GET result) */ + uint32_t reply_tx_size; + /** @brief Reply TX data pointer from user thread (e.g. LARGE_CONFIG_GET result) */ + void *reply_tx_data; struct ipc *ipc; struct k_thread *audio_thread; /** @brief Original kernel driver pointer for restoring dev->drv after create */ diff --git a/src/ipc/ipc-common.c b/src/ipc/ipc-common.c index 7e9c445d7168..70acc591e776 100644 --- a/src/ipc/ipc-common.c +++ b/src/ipc/ipc-common.c @@ -503,6 +503,19 @@ static void ipc_user_thread_fn(void *p1, void *p2, void *p3) IPC4_INVALID_RESOURCE_ID; break; } + case SOF_IPC4_MOD_LARGE_CONFIG_GET: + ipc_user->result = + ipc4_process_large_config_get( + &msg, + &ipc_user->reply_ext, + &ipc_user->reply_tx_size, + &ipc_user->reply_tx_data); + break; + case SOF_IPC4_MOD_LARGE_CONFIG_SET: + ipc_user->result = + ipc4_process_large_config_set( + &msg); + break; default: LOG_ERR("IPC user: unsupported module cmd type %d", msg.primary.r.type); diff --git a/src/ipc/ipc4/handler-user.c b/src/ipc/ipc4/handler-user.c index 7711ac53289d..c8b5ca875ce1 100644 --- a/src/ipc/ipc4/handler-user.c +++ b/src/ipc/ipc4/handler-user.c @@ -1016,6 +1016,108 @@ __cold static int ipc4_get_vendor_config_module_instance(struct comp_dev *dev, return IPC4_SUCCESS; } +__cold int ipc4_process_large_config_get(struct ipc4_message_request *ipc4, + uint32_t *reply_ext, + uint32_t *reply_tx_size, + void **reply_tx_data) +{ + struct ipc4_module_large_config_reply reply; + struct ipc4_module_large_config config; + char *data = ipc_get()->comp_data; + const struct comp_driver *drv; + struct comp_dev *dev = NULL; + uint32_t data_offset; + + assert_can_be_cold(); + + int ret = memcpy_s(&config, sizeof(config), ipc4, sizeof(*ipc4)); + + if (ret < 0) + return IPC4_FAILURE; + + tr_dbg(&ipc_tr, "%x : %x", + (uint32_t)config.primary.r.module_id, (uint32_t)config.primary.r.instance_id); + + /* get component dev for non-basefw since there is no + * component dev for basefw + */ + if (config.primary.r.module_id) { + uint32_t comp_id; + + comp_id = IPC4_COMP_ID(config.primary.r.module_id, + config.primary.r.instance_id); + dev = ipc4_get_comp_dev(comp_id); + if (!dev) + return IPC4_MOD_INVALID_ID; + + drv = dev->drv; + + /* Multicore disabled for userspace forwarding; + * non-userspace path retains ipc4_process_on_core() + */ + } else { + drv = ipc4_get_comp_drv(config.primary.r.module_id); + } + + if (!drv) + return IPC4_MOD_INVALID_ID; + + if (!drv->ops.get_large_config) + return IPC4_INVALID_REQUEST; + + data_offset = config.extension.r.data_off_size; + + /* check for vendor param first */ + if (config.extension.r.large_param_id == VENDOR_CONFIG_PARAM) { + /* For now only vendor_config case uses payload from hostbox */ + dcache_invalidate_region((__sparse_force void __sparse_cache *)MAILBOX_HOSTBOX_BASE, + config.extension.r.data_off_size); + ret = ipc4_get_vendor_config_module_instance(dev, drv, + config.extension.r.init_block, + config.extension.r.final_block, + &data_offset, + data, + (const char *)MAILBOX_HOSTBOX_BASE); + } else { +#if CONFIG_LIBRARY + data += sizeof(reply); +#endif + ipc4_prepare_for_kcontrol_get(dev, config.extension.r.large_param_id, + data, data_offset); + + ret = drv->ops.get_large_config(dev, config.extension.r.large_param_id, + config.extension.r.init_block, + config.extension.r.final_block, + &data_offset, data); + } + + /* set up ipc4 error code for reply data */ + if (ret < 0) + ret = IPC4_MOD_INVALID_ID; + + /* Copy host config and overwrite */ + reply.extension.dat = config.extension.dat; + reply.extension.r.data_off_size = data_offset; + + /* The last block, no more data */ + if (!config.extension.r.final_block && data_offset < SOF_IPC_MSG_MAX_SIZE) + reply.extension.r.final_block = 1; + + /* Indicate last block if error occurs */ + if (ret) + reply.extension.r.final_block = 1; + + /* no need to allocate memory for reply msg */ + if (ret) + return ret; + + /* Output via parameters instead of msg_reply */ + *reply_ext = reply.extension.dat; + *reply_tx_size = data_offset; + *reply_tx_data = data; + return ret; +} + __cold static int ipc4_get_large_config_module_instance(struct ipc4_message_request *ipc4) { struct ipc4_module_large_config_reply reply; @@ -1179,6 +1281,77 @@ __cold static int ipc4_set_vendor_config_module_instance(struct comp_dev *dev, data_off_size, data); } +__cold int ipc4_process_large_config_set(struct ipc4_message_request *ipc4) +{ + struct ipc4_module_large_config config; + struct comp_dev *dev = NULL; + const struct comp_driver *drv; + + assert_can_be_cold(); + + int ret = memcpy_s(&config, sizeof(config), ipc4, sizeof(*ipc4)); + + if (ret < 0) + return IPC4_FAILURE; + + dcache_invalidate_region((__sparse_force void __sparse_cache *)MAILBOX_HOSTBOX_BASE, + config.extension.r.data_off_size); + tr_dbg(&ipc_tr, "%x : %x", + (uint32_t)config.primary.r.module_id, (uint32_t)config.primary.r.instance_id); + + if (config.primary.r.module_id) { + uint32_t comp_id; + + comp_id = IPC4_COMP_ID(config.primary.r.module_id, config.primary.r.instance_id); + dev = ipc4_get_comp_dev(comp_id); + if (!dev) + return IPC4_MOD_INVALID_ID; + + drv = dev->drv; + + /* Multicore disabled for userspace forwarding; + * non-userspace path retains ipc4_process_on_core() + */ + } else { + drv = ipc4_get_comp_drv(config.primary.r.module_id); + } + + if (!drv) + return IPC4_MOD_INVALID_ID; + + if (!drv->ops.set_large_config) + return IPC4_INVALID_REQUEST; + + /* check for vendor param first */ + if (config.extension.r.large_param_id == VENDOR_CONFIG_PARAM) { + ret = ipc4_set_vendor_config_module_instance(dev, drv, + (uint32_t)config.primary.r.module_id, + (uint32_t)config.primary.r.instance_id, + config.extension.r.init_block, + config.extension.r.final_block, + config.extension.r.data_off_size, + (const char *)MAILBOX_HOSTBOX_BASE); + } else { +#if CONFIG_LIBRARY + struct ipc *ipc = ipc_get(); + const char *data = (const char *)ipc->comp_data + sizeof(config); +#else + const char *data = (const char *)MAILBOX_HOSTBOX_BASE; +#endif + ret = drv->ops.set_large_config(dev, config.extension.r.large_param_id, + config.extension.r.init_block, config.extension.r.final_block, + config.extension.r.data_off_size, data); + if (ret < 0) { + ipc_cmd_err(&ipc_tr, "failed to set large_config_module_instance %x : %x", + (uint32_t)config.primary.r.module_id, + (uint32_t)config.primary.r.instance_id); + ret = IPC4_INVALID_RESOURCE_ID; + } + } + + return ret; +} + __cold static int ipc4_set_large_config_module_instance(struct ipc4_message_request *ipc4) { struct ipc4_module_large_config config; @@ -1371,10 +1544,50 @@ __cold int ipc4_user_process_module_message(struct ipc4_message_request *ipc4, #endif break; case SOF_IPC4_MOD_LARGE_CONFIG_GET: +#ifdef CONFIG_SOF_USERSPACE_LL + { + struct ipc4_module_large_config config; + + memcpy_s(&config, sizeof(config), ipc4, sizeof(*ipc4)); + if (config.primary.r.module_id) { + /* Module case: forward to user thread */ + ret = ipc_user_forward_cmd(ipc4); + if (!ret) { + struct ipc *ipc = ipc_get(); + struct ipc_user *pdata = ipc->ipc_user_pdata; + + msg_reply->extension = pdata->reply_ext; + msg_reply->tx_size = pdata->reply_tx_size; + msg_reply->tx_data = pdata->reply_tx_data; + } + } else { + /* Base firmware (module_id==0): keep in kernel — + * ipc4_get_comp_drv() accesses IMR manifest which + * has no user-space partition. + */ + ret = ipc4_get_large_config_module_instance(ipc4); + } + } +#else ret = ipc4_get_large_config_module_instance(ipc4); +#endif break; case SOF_IPC4_MOD_LARGE_CONFIG_SET: +#ifdef CONFIG_SOF_USERSPACE_LL + { + struct ipc4_module_large_config config; + + memcpy_s(&config, sizeof(config), ipc4, sizeof(*ipc4)); + if (config.primary.r.module_id) { + ret = ipc_user_forward_cmd(ipc4); + } else { + /* Base firmware: keep in kernel (IMR access) */ + ret = ipc4_set_large_config_module_instance(ipc4); + } + } +#else ret = ipc4_set_large_config_module_instance(ipc4); +#endif break; case SOF_IPC4_MOD_BIND: #ifdef CONFIG_SOF_USERSPACE_LL From a5abb94ed5176fea28e75dbe76253982799442cf Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Fri, 27 Mar 2026 12:45:25 +0200 Subject: [PATCH 098/106] (---section: IPC user support STOP) From 821c7888b76235d3276fcf9bc5dde4ba6226ea69 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 19 Feb 2026 16:24:36 +0200 Subject: [PATCH 099/106] (---section test-case START) From 42075cffd504ff810131d7f0683e1d16f5f3d853 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Wed, 22 Apr 2026 15:17:31 +0300 Subject: [PATCH 100/106] zephyr: test: userspace: add pipeline_two_components test Add a new test to userspace_ll set that takes a step towards running full audio pipelines in user-space. The test creates a pipeline with two components (IPC4 host and DAI copiers), does pipeline prepare, one copy cycle in prepared state and tears down the pipeline. One user-space thread is created to manage the pipelines. This would be equivalent to user IPC handler thread. Another user-space thread is created for the LL scheduler. This test has some limitation, but is a useful test point to test resource allocations in pipeline, component and module adapter code. The code adds a reference test case where the same flow is fully run in kernel space. Signed-off-by: Kai Vehmanen --- zephyr/test/userspace/test_ll_task.c | 425 ++++++++++++++++++++++++++- 1 file changed, 423 insertions(+), 2 deletions(-) diff --git a/zephyr/test/userspace/test_ll_task.c b/zephyr/test/userspace/test_ll_task.c index 234423defc60..5a4f5e058131 100644 --- a/zephyr/test/userspace/test_ll_task.c +++ b/zephyr/test/userspace/test_ll_task.c @@ -14,9 +14,19 @@ #include #include #include +#include +#include +#include +#include #include #include #include +#include +#include +#include +#include +#include +#include #include #include @@ -24,6 +34,7 @@ #include #include /* offsetof() */ +#include LOG_MODULE_DECLARE(sof_boot_test, LOG_LEVEL_DBG); @@ -36,10 +47,15 @@ K_APPMEM_PARTITION_DEFINE(userspace_ll_part); /* Global variable for test runs counter, accessible from user-space */ K_APP_BMEM(userspace_ll_part) static int test_runs; +/* User-space thread for pipeline_two_components test */ +#define PPL_USER_STACKSIZE 4096 + +static struct k_thread ppl_user_thread; +static K_THREAD_STACK_DEFINE(ppl_user_stack, PPL_USER_STACKSIZE); + static enum task_state task_callback(void *arg) { LOG_INF("entry"); - if (++test_runs > 3) return SOF_TASK_STATE_COMPLETED; @@ -77,7 +93,7 @@ static void ll_task_test(void) LOG_INF("task scheduled and running"); /* Let the task run for a bit */ - k_sleep(K_MSEC(10)); + k_sleep(K_MSEC(100)); /* Cancel the task to stop any scheduled execution */ ret = schedule_task_cancel(task); @@ -87,6 +103,9 @@ static void ll_task_test(void) ret = schedule_task_free(task); zassert_equal(ret, 0); + k_mem_domain_remove_partition(zephyr_ll_mem_domain(), &userspace_ll_part); + zephyr_ll_task_free(task); + LOG_INF("test complete"); } @@ -129,6 +148,408 @@ ZTEST(userspace_ll, pipeline_check) pipeline_check(); } +/* Copier UUID: 9ba00c83-ca12-4a83-943c-1fa2e82f9dda */ +static const uint8_t copier_uuid[16] = { + 0x83, 0x0c, 0xa0, 0x9b, 0x12, 0xca, 0x83, 0x4a, + 0x94, 0x3c, 0x1f, 0xa2, 0xe8, 0x2f, 0x9d, 0xda +}; + +/** + * Find the module_id (manifest entry index) for the copier module + * by iterating the firmware manifest and matching the copier UUID. + */ +static int find_copier_module_id(void) +{ + const struct sof_man_fw_desc *desc = basefw_vendor_get_manifest(); + const struct sof_man_module *mod; + uint32_t i; + + if (!desc) + return -1; + + for (i = 0; i < desc->header.num_module_entries; i++) { + mod = (const struct sof_man_module *)((const char *)desc + + SOF_MAN_MODULE_OFFSET(i)); + if (!memcmp(&mod->uuid, copier_uuid, sizeof(copier_uuid))) + return (int)i; + } + + return -1; +} + +/** + * IPC4 copier module config - used as payload for comp_new_ipc4(). + * Placed at MAILBOX_HOSTBOX_BASE before calling comp_new_ipc4(). + * Layout matches struct ipc4_copier_module_cfg from copier.h. + */ +struct copier_init_data { + struct ipc4_base_module_cfg base; + struct ipc4_audio_format out_fmt; + uint32_t copier_feature_mask; + /* Gateway config (matches struct ipc4_copier_gateway_cfg) */ + union ipc4_connector_node_id node_id; + uint32_t dma_buffer_size; + uint32_t config_length; +} __packed __aligned(4); + +static void fill_audio_format(struct ipc4_audio_format *fmt) +{ + memset(fmt, 0, sizeof(*fmt)); + fmt->sampling_frequency = IPC4_FS_48000HZ; + fmt->depth = IPC4_DEPTH_32BIT; + fmt->ch_cfg = IPC4_CHANNEL_CONFIG_STEREO; + fmt->channels_count = 2; + fmt->valid_bit_depth = 32; + fmt->s_type = IPC4_TYPE_MSB_INTEGER; + fmt->interleaving_style = IPC4_CHANNELS_INTERLEAVED; +} + +/** + * Create a copier component via IPC4. + * + * @param module_id Copier module_id from manifest + * @param instance_id Instance ID for this component + * @param pipeline_id Parent pipeline ID + * @param node_id Gateway node ID (type + virtual DMA index) + */ +static struct comp_dev *create_copier(int module_id, int instance_id, + int pipeline_id, + union ipc4_connector_node_id node_id) +{ + struct ipc4_module_init_instance module_init; + struct copier_init_data cfg; + struct comp_dev *dev; + + /* Prepare copier config payload */ + memset(&cfg, 0, sizeof(cfg)); + fill_audio_format(&cfg.base.audio_fmt); + /* 2 channels * 4 bytes * 48 frames = 384 bytes */ + cfg.base.ibs = 384; + cfg.base.obs = 384; + cfg.base.is_pages = 0; + cfg.base.cpc = 0; + cfg.out_fmt = cfg.base.audio_fmt; + cfg.copier_feature_mask = 0; + cfg.node_id = node_id; + cfg.dma_buffer_size = 768; + cfg.config_length = 0; + + /* Write config data to mailbox hostbox (where comp_new_ipc4 reads it). + * Flush cache so that data is visible in SRAM before comp_new_ipc4() + * invalidates the cache line (in normal IPC flow, host writes via DMA + * directly to SRAM, so the invalidation reads fresh data; here the DSP + * core itself writes, so an explicit flush is needed). + */ + memcpy((void *)MAILBOX_HOSTBOX_BASE, &cfg, sizeof(cfg)); + sys_cache_data_flush_range((void *)MAILBOX_HOSTBOX_BASE, sizeof(cfg)); + + /* Prepare IPC4 module init header */ + memset(&module_init, 0, sizeof(module_init)); + module_init.primary.r.module_id = module_id; + module_init.primary.r.instance_id = instance_id; + module_init.primary.r.type = SOF_IPC4_MOD_INIT_INSTANCE; + module_init.primary.r.msg_tgt = SOF_IPC4_MESSAGE_TARGET_MODULE_MSG; + module_init.primary.r.rsp = SOF_IPC4_MESSAGE_DIR_MSG_REQUEST; + + module_init.extension.r.param_block_size = sizeof(cfg) / sizeof(uint32_t); + module_init.extension.r.ppl_instance_id = pipeline_id; + module_init.extension.r.core_id = 0; + module_init.extension.r.proc_domain = 0; /* LL */ + + dev = comp_new_ipc4(&module_init); + + /* + * We use the IPC code to create the components. This code runs + * in kernel space, so we need to separately assign thecreated + * components to the user LL and IPC threads before it can be used. + */ + comp_grant_access_to_thread(dev, &ppl_user_thread); + + return dev; +} + +/** + * Context shared between kernel setup and the user-space pipeline thread. + */ +struct ppl_test_ctx { + struct pipeline *p; + struct k_heap *heap; + struct comp_dev *host_comp; + struct comp_dev *dai_comp; + struct comp_buffer *buf; + struct ipc *ipc; + struct ipc_comp_dev *ipc_pipe; +}; + +/** + * Pipeline operations: connect, complete, prepare, copy, verify, and clean up. + * This function is called either directly (kernel mode) or from a user-space + * thread, exercising pipeline_*() calls from the requested context. + */ +static void pipeline_ops(struct ppl_test_ctx *ctx) +{ + struct pipeline *p = ctx->p; + struct comp_dev *host_comp = ctx->host_comp; + struct comp_dev *dai_comp = ctx->dai_comp; + struct comp_buffer *buf = ctx->buf; + int ret; + + LOG_INF("pipeline_ops: user_context=%d", k_is_user_context()); + + /* Step: Connect host -> buffer -> DAI */ + ret = pipeline_connect(host_comp, buf, PPL_CONN_DIR_COMP_TO_BUFFER); + zassert_equal(ret, 0, "connect host to buffer failed"); + + ret = pipeline_connect(dai_comp, buf, PPL_CONN_DIR_BUFFER_TO_COMP); + zassert_equal(ret, 0, "connect buffer to DAI failed"); + + LOG_INF("host -> buffer -> DAI connected"); + + /* Step: Complete the pipeline */ + ret = pipeline_complete(p, host_comp, dai_comp); + zassert_equal(ret, 0, "pipeline complete failed"); + + /* Step: Prepare the pipeline */ + p->sched_comp = host_comp; + + ret = pipeline_prepare(p, host_comp); + zassert_equal(ret, 0, "pipeline prepare failed"); + + ret = pipeline_trigger(p, host_comp, COMP_TRIGGER_PRE_START); + //zassert_equal(ret, 0, "pipeline TRIGGER_START failed"); + + LOG_INF("pipeline complete, status = %d", p->status); + + /* Step: Run copies */ + pipeline_schedule_copy(p, 1000); + + /* Step: let run for 3 msec */ + k_sleep(K_MSEC(3)); + + /* Verify pipeline source and sink assignments */ + zassert_equal(p->source_comp, host_comp, "source comp mismatch"); + zassert_equal(p->sink_comp, dai_comp, "sink comp mismatch"); + + LOG_INF("pipeline_ops done"); +} + +/** + * User-space thread entry point for pipeline_two_components test. + * p1 points to the ppl_test_ctx shared with the kernel launcher. + */ +static void pipeline_user_thread(void *p1, void *p2, void *p3) +{ + struct ppl_test_ctx *ctx = (struct ppl_test_ctx *)p1; + + zassert_true(k_is_user_context(), "expected user context"); + pipeline_ops(ctx); +} + +/** + * Test creating a pipeline with a host copier and a DAI (link) copier, + * connected through a shared buffer. + * + * When run_in_user is true, all pipeline_*() calls are made from a + * separate user-space thread. + */ +static void pipeline_two_components(bool run_in_user) +{ + struct ppl_test_ctx *ctx; + struct k_heap *heap = NULL; + uint32_t pipeline_id = 2; + uint32_t priority = 0; + struct task *task; + uint32_t comp_id; + int copier_module_id; + int host_instance_id = 0; + int dai_instance_id = 1; + int core = 0; + int ret; + + /* Step: Find the copier module_id from the firmware manifest */ + copier_module_id = find_copier_module_id(); + zassert_true(copier_module_id >= 0, "copier module not found in manifest"); + LOG_INF("copier module_id = %d", copier_module_id); + + /* Step: Create pipeline */ + if (run_in_user) { + LOG_INF("running test with user memory domain"); + heap = zephyr_ll_user_heap(); + zassert_not_null(heap, "user heap not found"); + + task = zephyr_ll_task_alloc(); + zassert_not_null(task, "task allocation failed"); + } else { + task = sof_heap_alloc(NULL, SOF_MEM_FLAG_USER, sizeof(struct task), sizeof(void *)); + LOG_INF("running test with kernel memory domain"); + } + + ctx = sof_heap_alloc(heap, SOF_MEM_FLAG_USER, sizeof(*ctx), 0); + ctx->heap = heap; + ctx->ipc = ipc_get(); + + comp_id = IPC4_COMP_ID(copier_module_id, host_instance_id); + ctx->p = pipeline_new(ctx->heap, pipeline_id, priority, comp_id, NULL); + zassert_not_null(ctx->p, "pipeline creation failed"); + + /* create the LL scheduler thread by initializing one task */ + k_mem_domain_add_partition(zephyr_ll_mem_domain(), &userspace_ll_part); + + test_runs = 0; + ret = schedule_task_init_ll(task, SOF_UUID(test_task_uuid), SOF_SCHEDULE_LL_TIMER, + priority, task_callback, + (void *)&test_runs, core, 0); + zassert_equal(ret, 0); + + LOG_INF("task init done"); + + /* Set pipeline period so components get correct dev->period and dev->frames. + * This mirrors what ipc4_create_pipeline() does in normal IPC flow. + */ + ctx->p->time_domain = SOF_TIME_DOMAIN_TIMER; + ctx->p->period = LL_TIMER_PERIOD_US; + + /* Register pipeline in IPC component list so comp_new_ipc4() can + * find it via ipc_get_comp_by_ppl_id() and set dev->period. + */ + ctx->ipc_pipe = rzalloc(SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, + sizeof(struct ipc_comp_dev)); + zassert_not_null(ctx->ipc_pipe, "ipc_comp_dev alloc failed"); + ctx->ipc_pipe->pipeline = ctx->p; + ctx->ipc_pipe->type = COMP_TYPE_PIPELINE; + ctx->ipc_pipe->id = pipeline_id; + ctx->ipc_pipe->core = 0; + list_item_append(&ctx->ipc_pipe->list, &ctx->ipc->comp_list); + + /* Step: Create host copier with HDA host output gateway */ + union ipc4_connector_node_id host_node_id = { .f = { + .dma_type = ipc4_hda_host_output_class, + .v_index = 0 + }}; + ctx->host_comp = create_copier(copier_module_id, host_instance_id, pipeline_id, + host_node_id); + zassert_not_null(ctx->host_comp, "host copier creation failed"); + + /* Assign pipeline to host component */ + ctx->host_comp->pipeline = ctx->p; + ctx->host_comp->ipc_config.type = SOF_COMP_HOST; + + LOG_INF("host copier created, comp_id = 0x%x", ctx->host_comp->ipc_config.id); + + /* Step: Create link copier with HDA link output gateway */ + union ipc4_connector_node_id link_node_id = { .f = { + .dma_type = ipc4_hda_link_output_class, + .v_index = 0 + }}; + ctx->dai_comp = create_copier(copier_module_id, dai_instance_id, pipeline_id, + link_node_id); + zassert_not_null(ctx->dai_comp, "DAI copier creation failed"); + + /* Assign pipeline to DAI component */ + ctx->dai_comp->pipeline = ctx->p; + ctx->dai_comp->ipc_config.type = SOF_COMP_DAI; + + LOG_INF("DAI copier created, comp_id = 0x%x", ctx->dai_comp->ipc_config.id); + + /* Step: Allocate a buffer to connect host -> DAI */ + ctx->buf = buffer_alloc(ctx->heap, 384, 0, 0, false); + zassert_not_null(ctx->buf, "buffer allocation failed"); + + if (run_in_user) { + struct k_thread *task_thread; + + /* Create a user-space thread to execute pipeline operations */ + k_thread_create(&ppl_user_thread, ppl_user_stack, PPL_USER_STACKSIZE, + pipeline_user_thread, ctx, NULL, NULL, + -1, K_USER, K_FOREVER); + + /* Add thread to LL memory domain so it can access pipeline memory */ + k_mem_domain_add_thread(zephyr_ll_mem_domain(), &ppl_user_thread); + + user_grant_dai_access_all(&ppl_user_thread); + user_grant_dma_access_all(&ppl_user_thread); + user_access_to_mailbox(zephyr_ll_mem_domain(), &ppl_user_thread); + zephyr_ll_grant_access(&ppl_user_thread); + + task_thread = scheduler_init_context(task); + zassert_not_null(task_thread); + + /* + * A hack for testing purposes, normally DAI module + * is created in user-space so it gets access + * automatically. Until that works, use dai_dd directly. + */ + struct dai_data *dai_dd = comp_get_drvdata(ctx->dai_comp); + struct k_mutex *dai_lock = dai_dd->dai->lock; + LOG_INF("dai_lock mutex %p", dai_lock); + k_thread_access_grant(task_thread, dai_lock); + k_thread_access_grant(&ppl_user_thread, dai_lock); + comp_grant_access_to_thread(ctx->dai_comp, task_thread); + comp_grant_access_to_thread(ctx->host_comp, task_thread); + + k_thread_start(&ppl_user_thread); + + LOG_INF("user thread started, waiting for completion"); + + k_thread_join(&ppl_user_thread, K_FOREVER); + } else { + /* Run pipeline operations directly in kernel context */ + pipeline_ops(ctx); + } + + /* Step: Clean up - reset, disconnect, free buffer, free components, free pipeline */ + /* Reset pipeline to bring components back to COMP_STATE_READY, + * required before ipc_comp_free() which rejects non-READY components. + */ + ret = pipeline_reset(ctx->p, ctx->host_comp); + zassert_equal(ret, 0, "pipeline reset failed"); + + pipeline_disconnect(ctx->host_comp, ctx->buf, PPL_CONN_DIR_COMP_TO_BUFFER); + pipeline_disconnect(ctx->dai_comp, ctx->buf, PPL_CONN_DIR_BUFFER_TO_COMP); + + buffer_free(ctx->buf); + + /* Free components through IPC to properly remove from IPC device list */ + ret = ipc_comp_free(ctx->ipc, ctx->host_comp->ipc_config.id); + zassert_equal(ret, 0, "host comp free failed"); + + ret = ipc_comp_free(ctx->ipc, ctx->dai_comp->ipc_config.id); + zassert_equal(ret, 0, "DAI comp free failed"); + + /* Unregister pipeline from IPC component list */ + list_item_del(&ctx->ipc_pipe->list); + rfree(ctx->ipc_pipe); + + ret = pipeline_free(ctx->p); + zassert_equal(ret, 0, "pipeline free failed"); + + scheduler_free_context(); + + ret = schedule_task_free(task); + zassert_equal(ret, 0); + + sof_heap_free(heap, ctx); + + if (run_in_user) { + zephyr_ll_task_free(task); + k_mem_domain_remove_partition(zephyr_ll_mem_domain(), &userspace_ll_part); + } else { + sof_heap_free(NULL, task); + } + + LOG_INF("two component pipeline test complete"); +} + +ZTEST(userspace_ll, pipeline_two_components_kernel) +{ + pipeline_two_components(false); +} + +ZTEST(userspace_ll, pipeline_two_components_user) +{ + pipeline_two_components(true); +} + ZTEST_SUITE(userspace_ll, NULL, NULL, NULL, NULL, NULL); /** From 5b1495375b7a56bc4adde307bd911a2fb5d38f34 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Fri, 13 Mar 2026 15:56:40 +0200 Subject: [PATCH 101/106] zephyr: test: userspace: add ipc4_create_pipeline_check Add a test that sends SOF_IPC4_GLB_CREATE_PIPELINE message via ipc_cmd() interface. This test can be used to test SOF when built with CONFIG_SOF_USERSPACE_LL. Handling of audio pipeline IPC messages (like CREATE_PIPELINE) will be routed to a user-space IPC thread. Signed-off-by: Kai Vehmanen --- zephyr/test/userspace/test_ll_task.c | 78 ++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/zephyr/test/userspace/test_ll_task.c b/zephyr/test/userspace/test_ll_task.c index 5a4f5e058131..4bef2a953f61 100644 --- a/zephyr/test/userspace/test_ll_task.c +++ b/zephyr/test/userspace/test_ll_task.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -148,6 +149,83 @@ ZTEST(userspace_ll, pipeline_check) pipeline_check(); } +/** + * Test creating a pipeline via IPC4 GLB_CREATE_PIPELINE message. + * + * Unlike pipeline_check() which calls pipeline_new() directly, + * this test constructs an ipc4_pipeline_create message and sends + * it through ipc_cmd(), exercising the full IPC4 command dispatch + * path: ipc_cmd() -> ipc4_process_glb_message() -> + * ipc_user_forward_cmd() (userspace) or ipc4_new_pipeline(). + */ +static void ipc4_create_pipeline_check(void) +{ + struct ipc4_pipeline_create pipe_desc = {0}; + struct ipc *ipc = ipc_get(); + struct ipc_cmd_hdr *hdr; + struct ipc_comp_dev *ipc_pipe; + uint32_t pipeline_id = 10; + uint32_t priority = 3; + int ret; + + /* Construct IPC4 CREATE_PIPELINE message */ + pipe_desc.primary.r.type = SOF_IPC4_GLB_CREATE_PIPELINE; + pipe_desc.primary.r.msg_tgt = SOF_IPC4_MESSAGE_TARGET_FW_GEN_MSG; + pipe_desc.primary.r.rsp = SOF_IPC4_MESSAGE_DIR_MSG_REQUEST; + pipe_desc.primary.r.instance_id = pipeline_id; + pipe_desc.primary.r.ppl_priority = priority; + pipe_desc.primary.r.ppl_mem_size = 0; + + pipe_desc.extension.r.core_id = 0; + pipe_desc.extension.r.lp = 0; + pipe_desc.extension.r.payload = 0; + + /* + * Populate handler.c's internal IPC message buffer. + * ipc_compact_read_msg() returns a pointer to the static + * msg_data.msg_in used by ipc_cmd() via ipc4_get_message_request(). + * Overwriting through this pointer sets up the message for dispatch. + */ + hdr = ipc_compact_read_msg(); + hdr->pri = pipe_desc.primary.dat; + hdr->ext = pipe_desc.extension.dat; + + /* Send through the full IPC command dispatch path */ + ipc_cmd(hdr); + + LOG_INF("ipc_cmd() returned for pipeline id=%u", pipeline_id); + + /* Verify pipeline is registered in IPC component list */ + ipc_pipe = ipc_get_pipeline_by_id(ipc, pipeline_id); + zassert_not_null(ipc_pipe, "pipeline not found in IPC comp list"); + zassert_equal(ipc_pipe->type, COMP_TYPE_PIPELINE, "wrong comp type"); + zassert_equal(ipc_pipe->id, pipeline_id, "pipeline id mismatch"); + zassert_not_null(ipc_pipe->pipeline, "pipeline struct is NULL"); + zassert_equal(ipc_pipe->pipeline->pipeline_id, pipeline_id, + "pipeline->pipeline_id mismatch"); + zassert_equal(ipc_pipe->pipeline->priority, priority, + "pipeline priority mismatch"); + zassert_equal(ipc_pipe->pipeline->time_domain, SOF_TIME_DOMAIN_TIMER, + "time_domain not set"); + + LOG_INF("pipeline verified in IPC comp list"); + + /* Clean up through IPC free path */ + ret = ipc_pipeline_free(ipc, pipeline_id); + zassert_equal(ret, 0, "ipc_pipeline_free failed: %%d", ret); + + /* Verify pipeline is removed from IPC component list */ + ipc_pipe = ipc_get_pipeline_by_id(ipc, pipeline_id); + zassert_is_null(ipc_pipe, "pipeline still in IPC comp list after free"); + + LOG_INF("ipc4 create pipeline test complete"); +} + +ZTEST(userspace_ll, ipc4_create_pipeline_check) +{ + ipc4_create_pipeline_check(); +} + /* Copier UUID: 9ba00c83-ca12-4a83-943c-1fa2e82f9dda */ static const uint8_t copier_uuid[16] = { 0x83, 0x0c, 0xa0, 0x9b, 0x12, 0xca, 0x83, 0x4a, From b75f472bce70e97987eda409f8ff8b01893ff904 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 19 Feb 2026 16:25:31 +0200 Subject: [PATCH 102/106] (---section WIP mandatory changes START) From 7ef89f8f6662655a9240fec3288b3c5298c411dd Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Tue, 17 Mar 2026 19:47:17 +0200 Subject: [PATCH 103/106] audio: pipeline: enable position reporting for user-space pipelines Place the pipeline position lookup table in the sysuser memory partition and replace k_spinlock with a dynamically allocated k_mutex when CONFIG_SOF_USERSPACE_LL is enabled. Spinlocks disable interrupts which is a privileged operation unavailable from user-mode threads. The mutex pointer is stored in a separate APP_SYSUSER_BSS variable outside the SHARED_DATA struct so Zephyr's kernel object tracking can recognize it for syscall verification. Move pipeline_posn_init() from task_main_start() to primary_core_init() before platform_init(), so the mutex is allocated before ipc_user_init() grants thread access to it. In pipeline_posn_get(), bypass the sof_get() kernel singleton and access the shared structure directly when running in user-space. Grant the ipc_user_init thread access to the pipeline position mutex via new pipeline_posn_grant_access() helper. Signed-off-by: Kai Vehmanen --- src/audio/pipeline/pipeline-graph.c | 58 +++++++++++++++++++++++++++-- src/include/sof/audio/pipeline.h | 8 ++++ src/init/init.c | 6 +++ src/ipc/ipc-common.c | 1 + zephyr/wrapper.c | 3 -- 5 files changed, 69 insertions(+), 7 deletions(-) diff --git a/src/audio/pipeline/pipeline-graph.c b/src/audio/pipeline/pipeline-graph.c index cf493bfb6ea4..11ba84421c46 100644 --- a/src/audio/pipeline/pipeline-graph.c +++ b/src/audio/pipeline/pipeline-graph.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -41,10 +42,20 @@ DECLARE_TR_CTX(pipe_tr, SOF_UUID(pipe_uuid), LOG_LEVEL_INFO); /* lookup table to determine busy/free pipeline metadata objects */ struct pipeline_posn { bool posn_offset[PPL_POSN_OFFSETS]; /**< available offsets */ +#ifndef CONFIG_SOF_USERSPACE_LL struct k_spinlock lock; /**< lock mechanism */ +#endif }; /* the pipeline position lookup table */ -static SHARED_DATA struct pipeline_posn pipeline_posn_shared; +static APP_SYSUSER_BSS SHARED_DATA struct pipeline_posn pipeline_posn_shared; + +#ifdef CONFIG_SOF_USERSPACE_LL +/* Mutex pointer in user-accessible partition so user-space threads + * can read the pointer for syscalls. Kept outside the SHARED_DATA + * struct to avoid kernel object tracking issues. + */ +static APP_SYSUSER_BSS struct k_mutex *pipeline_posn_lock; +#endif /** * \brief Retrieves pipeline position structure. @@ -52,7 +63,12 @@ static SHARED_DATA struct pipeline_posn pipeline_posn_shared; */ static inline struct pipeline_posn *pipeline_posn_get(void) { +#ifdef CONFIG_SOF_USERSPACE_LL + return platform_shared_get(&pipeline_posn_shared, + sizeof(pipeline_posn_shared)); +#else return sof_get()->pipeline_posn; +#endif } /** @@ -65,9 +81,14 @@ static inline int pipeline_posn_offset_get(uint32_t *posn_offset) struct pipeline_posn *pipeline_posn = pipeline_posn_get(); int ret = -EINVAL; uint32_t i; + +#ifdef CONFIG_SOF_USERSPACE_LL + k_mutex_lock(pipeline_posn_lock, K_FOREVER); +#else k_spinlock_key_t key; key = k_spin_lock(&pipeline_posn->lock); +#endif for (i = 0; i < PPL_POSN_OFFSETS; ++i) { if (!pipeline_posn->posn_offset[i]) { @@ -78,8 +99,11 @@ static inline int pipeline_posn_offset_get(uint32_t *posn_offset) } } - +#ifdef CONFIG_SOF_USERSPACE_LL + k_mutex_unlock(pipeline_posn_lock); +#else k_spin_unlock(&pipeline_posn->lock, key); +#endif return ret; } @@ -92,22 +116,43 @@ static inline void pipeline_posn_offset_put(uint32_t posn_offset) { struct pipeline_posn *pipeline_posn = pipeline_posn_get(); int i = posn_offset / sizeof(struct sof_ipc_stream_posn); + +#ifdef CONFIG_SOF_USERSPACE_LL + k_mutex_lock(pipeline_posn_lock, K_FOREVER); + pipeline_posn->posn_offset[i] = false; + k_mutex_unlock(pipeline_posn_lock); +#else k_spinlock_key_t key; key = k_spin_lock(&pipeline_posn->lock); - pipeline_posn->posn_offset[i] = false; - k_spin_unlock(&pipeline_posn->lock, key); +#endif } void pipeline_posn_init(struct sof *sof) { sof->pipeline_posn = platform_shared_get(&pipeline_posn_shared, sizeof(pipeline_posn_shared)); +#ifdef CONFIG_SOF_USERSPACE_LL + pipeline_posn_lock = k_object_alloc(K_OBJ_MUTEX); + if (!pipeline_posn_lock) { + pipe_cl_err("pipeline posn mutex alloc failed"); + k_panic(); + } + k_mutex_init(pipeline_posn_lock); +#else k_spinlock_init(&sof->pipeline_posn->lock); +#endif } +#ifdef CONFIG_SOF_USERSPACE_LL +void pipeline_posn_grant_access(struct k_thread *thread) +{ + k_thread_access_grant(thread, pipeline_posn_lock); +} +#endif + /* create new pipeline - returns pipeline id or negative error */ struct pipeline *pipeline_new(struct k_heap *heap, uint32_t pipeline_id, uint32_t priority, uint32_t comp_id, struct create_pipeline_params *pparams) @@ -138,12 +183,17 @@ struct pipeline *pipeline_new(struct k_heap *heap, uint32_t pipeline_id, uint32_ p->pipeline_id = pipeline_id; p->status = COMP_STATE_INIT; p->trigger.cmd = COMP_TRIGGER_NO_ACTION; + +#ifdef CONFIG_SOF_USERSPACE_LL + LOG_WRN("pipeline trace settings cannot be copied"); +#else ret = memcpy_s(&p->tctx, sizeof(struct tr_ctx), &pipe_tr, sizeof(struct tr_ctx)); if (ret < 0) { pipe_err(p, "failed to copy trace settings"); goto free; } +#endif ret = pipeline_posn_offset_get(&p->posn_offset); if (ret < 0) { diff --git a/src/include/sof/audio/pipeline.h b/src/include/sof/audio/pipeline.h index 913a569c208c..ff456fbceb7d 100644 --- a/src/include/sof/audio/pipeline.h +++ b/src/include/sof/audio/pipeline.h @@ -206,6 +206,14 @@ int pipeline_complete(struct pipeline *p, struct comp_dev *source, */ void pipeline_posn_init(struct sof *sof); +#ifdef CONFIG_SOF_USERSPACE_LL +/** + * \brief Grants user-space thread access to pipeline position mutex. + * \param[in] thread Thread to grant access to. + */ +void pipeline_posn_grant_access(struct k_thread *thread); +#endif + /** * \brief Resets the pipeline and free runtime resources. * \param[in] p pipeline. diff --git a/src/init/init.c b/src/init/init.c index 58607df1254a..546b1931db41 100644 --- a/src/init/init.c +++ b/src/init/init.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #if CONFIG_IPC_MAJOR_4 #include @@ -240,6 +241,11 @@ __cold static int primary_core_init(int argc, char *argv[], struct sof *sof) zephyr_ll_user_resources_init(); #endif + /* init pipeline position offsets - must be before platform_init() + * which calls ipc_init() -> ipc_user_init() that needs the posn mutex. + */ + pipeline_posn_init(sof); + /* init the platform */ if (platform_init(sof) < 0) sof_panic(SOF_IPC_PANIC_PLATFORM); diff --git a/src/ipc/ipc-common.c b/src/ipc/ipc-common.c index 70acc591e776..34f964d3db04 100644 --- a/src/ipc/ipc-common.c +++ b/src/ipc/ipc-common.c @@ -596,6 +596,7 @@ __cold int ipc_user_init(void) user_grant_dma_access_all(&ipc_user_thread); user_access_to_mailbox(zephyr_ll_mem_domain(), &ipc_user_thread); zephyr_ll_grant_access(&ipc_user_thread); + pipeline_posn_grant_access(&ipc_user_thread); k_mem_domain_add_thread(zephyr_ll_mem_domain(), &ipc_user_thread); k_thread_cpu_pin(&ipc_user_thread, PLATFORM_PRIMARY_CORE_ID); diff --git a/zephyr/wrapper.c b/zephyr/wrapper.c index f814c7c52beb..ccbdfe478b08 100644 --- a/zephyr/wrapper.c +++ b/zephyr/wrapper.c @@ -176,9 +176,6 @@ int task_main_start(struct sof *sof) /* init default audio components */ sys_comp_init(sof); - /* init pipeline position offsets */ - pipeline_posn_init(sof); - return 0; } From 6de0ca42f7d6d5abec16b021c5938c2a85181146 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 16 Apr 2026 15:12:56 +0300 Subject: [PATCH 104/106] WIP: ipc: expose coldrodata to IPC user thread If SOF is built with CONFIG_SOF_USERSPACE_LL, the IPC user handled will require access to coldrodata sections to initialize audio modules. This logic is not required for LLEXT modules, which have existing code to add access to coldrodata (and other sections). This commit is needed for builds where LLEXT is not used. Signed-off-by: Kai Vehmanen --- src/ipc/ipc-common.c | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/ipc/ipc-common.c b/src/ipc/ipc-common.c index 34f964d3db04..44d9edf9ca57 100644 --- a/src/ipc/ipc-common.c +++ b/src/ipc/ipc-common.c @@ -575,6 +575,40 @@ __cold int ipc_user_init(void) ret = k_mem_domain_add_partition(zephyr_ll_mem_domain(), &ipc_context_part); + /* + * Grant user-space access to .cold (execute) and .coldrodata (read) + * sections in IMR. The prepare path walks component code that may + * reference __cold functions and __cold_rodata data. + */ +#ifdef CONFIG_COLD_STORE_EXECUTE_DRAM + { + extern char __cold_start[], __cold_end[]; + extern char __coldrodata_start[]; + extern char _imr_end[]; + struct k_mem_partition cold_part; + + cold_part.start = (uintptr_t)__cold_start; + cold_part.size = ALIGN_UP((uintptr_t)__cold_end - + (uintptr_t)__cold_start, + CONFIG_MMU_PAGE_SIZE); + cold_part.attr = K_MEM_PARTITION_P_RX_U_RX; + ret = k_mem_domain_add_partition(zephyr_ll_mem_domain(), + &cold_part); + if (ret < 0) + LOG_WRN("cold text partition add failed: %d", ret); + + cold_part.start = (uintptr_t)__coldrodata_start; + cold_part.size = ALIGN_UP((uintptr_t)_imr_end - + (uintptr_t)__coldrodata_start, + CONFIG_MMU_PAGE_SIZE); + cold_part.attr = K_MEM_PARTITION_P_RO_U_RO; + ret = k_mem_domain_add_partition(zephyr_ll_mem_domain(), + &cold_part); + if (ret < 0) + LOG_WRN("cold rodata partition add failed: %d", ret); + } +#endif + k_sem_init(ipc_user->sem, 0, 1); /* Allocate kernel objects for the user-space thread */ From 47b443757ca279d6afa86de169532ef79cbfab36 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 26 Feb 2026 17:14:39 +0200 Subject: [PATCH 105/106] HACK: audio: collection of feature limitations to run LL in user This is a set of temporary changes to audio code to remove calls to privileged interfaces that are not mandatory to run simple audio tests. These need proper solutions to be able to run all use-cases in user LL version. Signed-off-by: Kai Vehmanen --- src/audio/pipeline/pipeline-graph.c | 6 +++++- zephyr/include/sof/lib/cpu.h | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/audio/pipeline/pipeline-graph.c b/src/audio/pipeline/pipeline-graph.c index 11ba84421c46..024b36e64860 100644 --- a/src/audio/pipeline/pipeline-graph.c +++ b/src/audio/pipeline/pipeline-graph.c @@ -547,6 +547,10 @@ struct comp_dev *pipeline_get_dai_comp(uint32_t pipeline_id, int dir) */ struct comp_dev *pipeline_get_dai_comp_latency(uint32_t pipeline_id, uint32_t *latency) { +#ifdef CONFIG_SOF_USERSPACE_LL + LOG_WRN("latency cannot be computed in user-space pipelines!"); + *latency = 0; +#else struct ipc_comp_dev *ipc_sink; struct ipc_comp_dev *ipc_source; struct comp_dev *source; @@ -614,7 +618,7 @@ struct comp_dev *pipeline_get_dai_comp_latency(uint32_t pipeline_id, uint32_t *l /* Get a next sink component */ ipc_sink = ipc_get_ppl_sink_comp(ipc, source->pipeline->pipeline_id); } - +#endif return NULL; } EXPORT_SYMBOL(pipeline_get_dai_comp_latency); diff --git a/zephyr/include/sof/lib/cpu.h b/zephyr/include/sof/lib/cpu.h index c23405e85121..533cb29f3602 100644 --- a/zephyr/include/sof/lib/cpu.h +++ b/zephyr/include/sof/lib/cpu.h @@ -55,7 +55,11 @@ static inline bool cpu_is_primary(int id) static inline bool cpu_is_me(int id) { +#ifdef CONFIG_SOF_USERSPACE_LL + return true; +#else return id == cpu_get_id(); +#endif } int cpu_enable_core(int id); From 25e27c9d5bea642cbaee296f86c4e4c085449f16 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Wed, 22 Apr 2026 17:30:40 +0300 Subject: [PATCH 106/106] [DO NOT MERGE] zephyr.yml: use Zephyr PR107341 Pull in Zephyr PR. Link: https://github.com/zephyrproject-rtos/zephyr/pull/107341 Signed-off-by: Kai Vehmanen --- west.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/west.yml b/west.yml index 3f7546d03d38..413fd7ab1c41 100644 --- a/west.yml +++ b/west.yml @@ -43,7 +43,7 @@ manifest: - name: zephyr repo-path: zephyr - revision: 684c9e8f32e4373a21098559f748f06915f950c9 + revision: pull/107341/head remote: zephyrproject # Import some projects listed in zephyr/west.yml@revision