From c0a3bb017ec1295803813c248d05472680b9c9ba Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 19 Feb 2026 14:37:07 +0200 Subject: [PATCH 1/7] 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 | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/zephyr/syscall/sof_dma.c b/zephyr/syscall/sof_dma.c index ed69ffc78423..f12a29aa1efb 100644 --- a/zephyr/syscall/sof_dma.c +++ b/zephyr/syscall/sof_dma.c @@ -109,19 +109,28 @@ static inline void z_vrfy_sof_dma_release_channel(struct sof_dma *dma, */ static inline struct dma_block_config *deep_copy_dma_blk_cfg_list(struct dma_config *cfg) { - struct dma_block_config *kern_cfg = - rmalloc(0, sizeof(*kern_cfg) * cfg->block_count); + struct dma_block_config *kern_cfg; struct dma_block_config *kern_prev = NULL, *kern_next, *user_next; int i = 0; + if (!cfg->block_count) + return NULL; + + kern_cfg = rmalloc(0, sizeof(*kern_cfg) * cfg->block_count); if (!kern_cfg) return NULL; 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 ff0bc0c500a7934e385864aa90158dfe1f8af44d Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Tue, 17 Feb 2026 13:45:44 +0200 Subject: [PATCH 2/7] 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 bd7c02edcb2d..8957bd1042f5 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 @@ -311,6 +312,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) { @@ -319,10 +325,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; @@ -332,7 +340,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; } @@ -343,6 +351,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) { @@ -350,7 +363,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 e787b9c5d08449eef4fe7520c7b8e76e74f11563 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 21 May 2026 18:13:45 +0300 Subject: [PATCH 3/7] 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 | 59 ++++++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/src/audio/dai-zephyr.c b/src/audio/dai-zephyr.c index 454132de150e..09e5e2082c18 100644 --- a/src/audio/dai-zephyr.c +++ b/src/audio/dai-zephyr.c @@ -225,55 +225,66 @@ __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; + struct dai_properties props; + k_spinlock_key_t key; + int ret; + key = k_spin_lock(&dai->lock); + ret = dai_get_properties_copy(dai->dev, direction, stream_id, &props); k_spin_unlock(&dai->lock, key); + 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; + struct dai_properties props; k_spinlock_key_t key; - int fifo_depth; + int ret; if (!dai) return 0; key = k_spin_lock(&dai->lock); - props = dai_get_properties(dai->dev, direction, 0); - fifo_depth = props->fifo_depth; + ret = dai_get_properties_copy(dai->dev, direction, 0, &props); k_spin_unlock(&dai->lock, key); + if (ret < 0) + return 0; - return fifo_depth; + return props.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; + struct dai_properties props; + k_spinlock_key_t key; + int ret; + key = k_spin_lock(&dai->lock); + ret = dai_get_properties_copy(dai->dev, direction, 0, &props); k_spin_unlock(&dai->lock, key); + 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) { - 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; + struct dai_properties props; + k_spinlock_key_t key; + int ret; + key = k_spin_lock(&dai->lock); + ret = dai_get_properties_copy(dai->dev, direction, stream_id, &props); k_spin_unlock(&dai->lock, key); + if (ret < 0) + return ret; - return fifo_address; + return props.fifo_address; } /* this is called by DMA driver every time descriptor has completed */ @@ -1948,16 +1959,18 @@ 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; + struct dai_properties props; + uint32_t init_delay = 0; k_spinlock_key_t key; - uint32_t init_delay; + int ret; if (!dai) return 0; key = k_spin_lock(&dai->lock); - 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_spin_unlock(&dai->lock, key); return init_delay; From 1bf6ab44f2618faebb85650681dac98794e6131c Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Tue, 19 May 2026 22:21:05 +0300 Subject: [PATCH 4/7] rtos: umutex.h: add new locking interface The Zephyr locking interfaces do not have a variant that allows the lock object to be put in dynamically allocated user-space memory. Implement a variant on top of Zephyr k_mutex that provides this support for SOF. Provide a no-op wrapper for POSIX library builds (just like we do for other locking interfaces). Kconfig option SOF_USERSPACE_INTERFACE_MUTEX is added to control whether umutex.h is exposed to user-space or not. Signed-off-by: Kai Vehmanen --- posix/include/rtos/mutex.h | 27 +++++++++ posix/include/rtos/umutex.h | 13 ++++ zephyr/CMakeLists.txt | 6 ++ zephyr/Kconfig | 15 +++++ zephyr/include/rtos/mutex.h | 1 + zephyr/include/rtos/umutex.h | 94 +++++++++++++++++++++++++++++ zephyr/lib/umutex.c | 62 +++++++++++++++++++ zephyr/syscall/umutex.c | 43 +++++++++++++ zephyr/test/CMakeLists.txt | 7 +++ zephyr/test/userspace/test_umutex.c | 93 ++++++++++++++++++++++++++++ 10 files changed, 361 insertions(+) create mode 100644 posix/include/rtos/umutex.h create mode 100644 zephyr/include/rtos/umutex.h create mode 100644 zephyr/lib/umutex.c create mode 100644 zephyr/syscall/umutex.c create mode 100644 zephyr/test/userspace/test_umutex.c diff --git a/posix/include/rtos/mutex.h b/posix/include/rtos/mutex.h index 19b360bdaea5..85216c7d8acc 100644 --- a/posix/include/rtos/mutex.h +++ b/posix/include/rtos/mutex.h @@ -62,4 +62,31 @@ static inline int sys_mutex_unlock(struct sys_mutex *mutex) return 0; } +/** + * @brief User-space accessible mutex stub for host/testbench builds. + */ +struct sof_umutex { + struct k_mutex mutex; /**< Inline k_mutex for POSIX (no dynamic alloc needed) */ +}; + +static inline int sof_umutex_init(struct sof_umutex *umutex) +{ + return k_mutex_init(&umutex->mutex); +} + +static inline int sof_umutex_lock(struct sof_umutex *umutex, k_timeout_t timeout) +{ + return k_mutex_lock(&umutex->mutex, timeout); +} + +static inline int sof_umutex_unlock(struct sof_umutex *umutex) +{ + return k_mutex_unlock(&umutex->mutex); +} + +static inline void sof_umutex_free(struct sof_umutex *umutex) +{ + /* No-op on POSIX — no kernel objects to free */ +} + #endif diff --git a/posix/include/rtos/umutex.h b/posix/include/rtos/umutex.h new file mode 100644 index 000000000000..1b2007cf059a --- /dev/null +++ b/posix/include/rtos/umutex.h @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 Intel Corporation. +// + +#ifndef __POSIX_RTOS_UMUTEX_H__ +#define __POSIX_RTOS_UMUTEX_H__ + +#include + +/* sof_umutex type and operations are defined in rtos/mutex.h for POSIX */ + +#endif /* __POSIX_RTOS_UMUTEX_H__ */ diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index 91598a776db0..649c0cb12604 100644 --- a/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -549,6 +549,12 @@ if(CONFIG_SOF_USERSPACE_INTERFACE_DMA) zephyr_syscall_header(include/sof/lib/sof_dma.h) endif() +zephyr_library_sources(lib/umutex.c) +if(CONFIG_SOF_USERSPACE_INTERFACE_MUTEX) + zephyr_library_sources(syscall/umutex.c) + zephyr_syscall_header(include/rtos/umutex.h) +endif() + # Mandatory Files used on all platforms. # Commented files will be added/removed as integration dictates. zephyr_library_sources( diff --git a/zephyr/Kconfig b/zephyr/Kconfig index f1d1896c4234..111e38b0e28b 100644 --- a/zephyr/Kconfig +++ b/zephyr/Kconfig @@ -29,10 +29,25 @@ 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_INTERFACE_MUTEX + bool "User-space mutex interface" + depends on USERSPACE + help + Enables the sof_umutex API for dynamically-allocated + user-space accessible mutexes backed by k_object_alloc. + config SOF_USERSPACE_LL bool "Run Low-Latency pipelines in userspace threads" depends on USERSPACE select SOF_USERSPACE_INTERFACE_DMA + select SOF_USERSPACE_INTERFACE_MUTEX help Run Low-Latency (LL) pipelines in userspace threads. This adds memory protection between operating system resources and diff --git a/zephyr/include/rtos/mutex.h b/zephyr/include/rtos/mutex.h index a8886f768d1e..bf1f25813888 100644 --- a/zephyr/include/rtos/mutex.h +++ b/zephyr/include/rtos/mutex.h @@ -8,5 +8,6 @@ #include /* k_mutex_*() */ #include /* for sys_mutex */ +#include /* for sof_umutex */ #endif /* __ZEPHYR_RTOS_MUTEX_H__ */ diff --git a/zephyr/include/rtos/umutex.h b/zephyr/include/rtos/umutex.h new file mode 100644 index 000000000000..5bea79f36181 --- /dev/null +++ b/zephyr/include/rtos/umutex.h @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 Intel Corporation. +// + +#ifndef __ZEPHYR_RTOS_UMUTEX_H__ +#define __ZEPHYR_RTOS_UMUTEX_H__ + +#include + +/** + * @brief User-space accessible mutex with dynamic allocation. + * + * This mutex variant can be dynamically allocated at runtime. The state + * struct resides in user-accessible memory; access to the mutex is granted + * to any thread that can access this memory region (no per-thread + * k_thread_access_grant needed). + * + * The backing k_mutex is allocated via k_object_alloc(K_OBJ_MUTEX) during + * init and freed via k_object_free() during free. + */ +struct sof_umutex { + struct k_mutex *mutex; /**< Pointer to dynamically-allocated backing k_mutex */ +}; + +/** + * @brief Initialize a dynamic user-space mutex. + * + * Allocates the backing k_mutex kernel object. The sof_umutex struct + * must reside in memory accessible to the calling thread. + * + * @param umutex Pointer to the sof_umutex state (in user-accessible memory) + * @return 0 on success, -ENOMEM if allocation fails + */ +__syscall int sof_umutex_init(struct sof_umutex *umutex); + +/** + * @brief Lock a dynamic user-space mutex. + * + * @param umutex Pointer to the sof_umutex state + * @param timeout Timeout value (K_FOREVER for indefinite wait) + * @return 0 on success, -EAGAIN on timeout, -EINVAL if not initialized + */ +__syscall int sof_umutex_lock(struct sof_umutex *umutex, k_timeout_t timeout); + +/** + * @brief Unlock a dynamic user-space mutex. + * + * @param umutex Pointer to the sof_umutex state + * @return 0 on success, -EINVAL if not initialized, -EPERM if not owner + */ +__syscall int sof_umutex_unlock(struct sof_umutex *umutex); + +/** + * @brief Free a dynamic user-space mutex. + * + * Releases the backing k_mutex kernel object via k_object_free(). + * The sof_umutex state must not be used after this call. + * + * @param umutex Pointer to the sof_umutex state + */ +__syscall void sof_umutex_free(struct sof_umutex *umutex); + +int z_impl_sof_umutex_init(struct sof_umutex *umutex); +int z_impl_sof_umutex_lock(struct sof_umutex *umutex, k_timeout_t timeout); +int z_impl_sof_umutex_unlock(struct sof_umutex *umutex); +void z_impl_sof_umutex_free(struct sof_umutex *umutex); + +#ifdef CONFIG_SOF_USERSPACE_INTERFACE_MUTEX +#include +#else +static inline int sof_umutex_init(struct sof_umutex *umutex) +{ + return z_impl_sof_umutex_init(umutex); +} + +static inline int sof_umutex_lock(struct sof_umutex *umutex, k_timeout_t timeout) +{ + return z_impl_sof_umutex_lock(umutex, timeout); +} + +static inline int sof_umutex_unlock(struct sof_umutex *umutex) +{ + return z_impl_sof_umutex_unlock(umutex); +} + +static inline void sof_umutex_free(struct sof_umutex *umutex) +{ + z_impl_sof_umutex_free(umutex); +} + +#endif /* CONFIG_SOF_USERSPACE_INTERFACE_MUTEX */ + +#endif /* __ZEPHYR_RTOS_UMUTEX_H__ */ diff --git a/zephyr/lib/umutex.c b/zephyr/lib/umutex.c new file mode 100644 index 000000000000..0a1f9d686650 --- /dev/null +++ b/zephyr/lib/umutex.c @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 Intel Corporation. + +/** + * @file + * @brief SOF dynamic user-space mutex implementation. + * + * Provides the kernel-side implementation of sof_umutex operations. + * The backing k_mutex is dynamically allocated via k_object_alloc + * and freed via k_object_free. + */ + +#include +#include +#include + +int z_impl_sof_umutex_init(struct sof_umutex *umutex) +{ + struct k_mutex *m; + int ret; + + m = k_object_alloc(K_OBJ_MUTEX); + if (m == NULL) { + return -ENOMEM; + } + + ret = k_mutex_init(m); + if (ret) { + k_object_free(m); + return ret; + } + + umutex->mutex = m; + return 0; +} + +int z_impl_sof_umutex_lock(struct sof_umutex *umutex, k_timeout_t timeout) +{ + if (umutex->mutex == NULL) { + return -EINVAL; + } + + return k_mutex_lock(umutex->mutex, timeout); +} + +int z_impl_sof_umutex_unlock(struct sof_umutex *umutex) +{ + if (umutex->mutex == NULL) { + return -EINVAL; + } + + return k_mutex_unlock(umutex->mutex); +} + +void z_impl_sof_umutex_free(struct sof_umutex *umutex) +{ + if (umutex->mutex != NULL) { + k_object_free(umutex->mutex); + umutex->mutex = NULL; + } +} diff --git a/zephyr/syscall/umutex.c b/zephyr/syscall/umutex.c new file mode 100644 index 000000000000..0692068e21eb --- /dev/null +++ b/zephyr/syscall/umutex.c @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 Intel Corporation. + +/** + * @file + * @brief SOF dynamic user-space mutex syscall verification. + * + * Verify handlers ensure the calling user thread has write access to + * the sof_umutex state struct before forwarding to the implementation. + */ + +#include +#include + +static inline int z_vrfy_sof_umutex_init(struct sof_umutex *umutex) +{ + K_OOPS(K_SYSCALL_MEMORY_WRITE(umutex, sizeof(struct sof_umutex))); + return z_impl_sof_umutex_init(umutex); +} +#include + +static inline int z_vrfy_sof_umutex_lock(struct sof_umutex *umutex, + k_timeout_t timeout) +{ + K_OOPS(K_SYSCALL_MEMORY_WRITE(umutex, sizeof(struct sof_umutex))); + return z_impl_sof_umutex_lock(umutex, timeout); +} +#include + +static inline int z_vrfy_sof_umutex_unlock(struct sof_umutex *umutex) +{ + K_OOPS(K_SYSCALL_MEMORY_WRITE(umutex, sizeof(struct sof_umutex))); + return z_impl_sof_umutex_unlock(umutex); +} +#include + +static inline void z_vrfy_sof_umutex_free(struct sof_umutex *umutex) +{ + K_OOPS(K_SYSCALL_MEMORY_WRITE(umutex, sizeof(struct sof_umutex))); + z_impl_sof_umutex_free(umutex); +} +#include diff --git a/zephyr/test/CMakeLists.txt b/zephyr/test/CMakeLists.txt index f548c98c5e73..fbf0f0a60253 100644 --- a/zephyr/test/CMakeLists.txt +++ b/zephyr/test/CMakeLists.txt @@ -8,6 +8,13 @@ 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() + + if(CONFIG_SOF_USERSPACE_INTERFACE_MUTEX) + zephyr_library_sources(userspace/test_umutex.c) + endif() endif() if(CONFIG_SOF_BOOT_TEST_STANDALONE AND CONFIG_SOF_USERSPACE_INTERFACE_DMA) diff --git a/zephyr/test/userspace/test_umutex.c b/zephyr/test/userspace/test_umutex.c new file mode 100644 index 000000000000..5f655f1063c2 --- /dev/null +++ b/zephyr/test/userspace/test_umutex.c @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright(c) 2026 Intel Corporation. + */ + +/** + * @file + * @brief Test case for sof_umutex API from a Zephyr user-space thread. + * + * Validates that sof_umutex_init/lock/unlock/free work correctly when + * called from user-space context. + */ + +#include +#include +#include + +#include +#include +#include +#include + +LOG_MODULE_DECLARE(sof_boot_test, LOG_LEVEL_DBG); + +#define USER_STACKSIZE 2048 + +static struct k_thread umutex_user_thread; +static K_THREAD_STACK_DEFINE(umutex_user_stack, USER_STACKSIZE); + +/* Memory partition for test data accessible from user-space */ +K_APPMEM_PARTITION_DEFINE(umutex_test_part); + +/* Place the sof_umutex state in the user-accessible partition */ +K_APP_BMEM(umutex_test_part) static struct sof_umutex test_umutex; + +static void umutex_user_function(void *p1, void *p2, void *p3) +{ + int ret; + + __ASSERT(k_is_user_context(), "isn't user"); + + LOG_INF("umutex test thread %s (%s)", + k_is_user_context() ? "UserSpace!" : "privileged mode.", + CONFIG_BOARD_TARGET); + + /* Initialize the umutex — allocates backing k_mutex */ + ret = sof_umutex_init(&test_umutex); + zassert_equal(ret, 0, "sof_umutex_init failed: %d", ret); + + LOG_INF("sof_umutex_init succeeded"); + + /* Lock the mutex */ + ret = sof_umutex_lock(&test_umutex, K_FOREVER); + zassert_equal(ret, 0, "sof_umutex_lock failed: %d", ret); + + LOG_INF("sof_umutex_lock succeeded"); + + /* Unlock the mutex */ + ret = sof_umutex_unlock(&test_umutex); + zassert_equal(ret, 0, "sof_umutex_unlock failed: %d", ret); + + LOG_INF("sof_umutex_unlock succeeded"); + + /* Free the mutex — releases backing k_mutex */ + sof_umutex_free(&test_umutex); + + LOG_INF("sof_umutex_free done"); +} + +static void test_user_thread_umutex(void) +{ + /* Add test partition to LL memory domain so user thread can access test_umutex */ + k_mem_domain_add_partition(zephyr_ll_mem_domain(), &umutex_test_part); + + k_thread_create(&umutex_user_thread, umutex_user_stack, USER_STACKSIZE, + umutex_user_function, NULL, NULL, NULL, + -1, K_USER, K_FOREVER); + + /* Add thread to LL memory domain so it can access the partition */ + k_mem_domain_add_thread(zephyr_ll_mem_domain(), &umutex_user_thread); + + k_thread_start(&umutex_user_thread); + k_thread_join(&umutex_user_thread, K_FOREVER); + + k_mem_domain_remove_partition(zephyr_ll_mem_domain(), &umutex_test_part); +} + +ZTEST(sof_boot, user_space_umutex) +{ + test_user_thread_umutex(); + + ztest_test_pass(); +} From 7a138a3225b701852347e2e6f19e13aad7e082eb Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 21 May 2026 18:26:21 +0300 Subject: [PATCH 5/7] audio: dai-zephyr: convert spinlock into umutex for properties The spinlock used to protect access to DAI properties can be converted to a sof_umutex as this is only accessed from IPC and LL threads and both are normal Zephyr threads. As an additional benefit, use of sof_umutex allows to run the dai-zephyr module in user-space. Signed-off-by: Kai Vehmanen --- src/audio/dai-zephyr.c | 35 +++++++++++++++++--------------- src/include/sof/lib/dai-zephyr.h | 3 ++- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/audio/dai-zephyr.c b/src/audio/dai-zephyr.c index 09e5e2082c18..a3b984d3ab5d 100644 --- a/src/audio/dai-zephyr.c +++ b/src/audio/dai-zephyr.c @@ -226,12 +226,11 @@ __cold int dai_set_config(struct dai *dai, struct ipc_config_dai *common_config, int dai_get_handshake(struct dai *dai, int direction, int stream_id) { struct dai_properties props; - k_spinlock_key_t key; int ret; - key = k_spin_lock(&dai->lock); + sof_umutex_lock(&dai->lock, K_FOREVER); ret = dai_get_properties_copy(dai->dev, direction, stream_id, &props); - k_spin_unlock(&dai->lock, key); + sof_umutex_unlock(&dai->lock); if (ret < 0) return ret; @@ -242,15 +241,14 @@ int dai_get_handshake(struct dai *dai, int direction, int stream_id) int dai_get_fifo_depth(struct dai *dai, int direction) { struct dai_properties props; - k_spinlock_key_t key; int ret; if (!dai) return 0; - key = k_spin_lock(&dai->lock); + sof_umutex_lock(&dai->lock, K_FOREVER); ret = dai_get_properties_copy(dai->dev, direction, 0, &props); - k_spin_unlock(&dai->lock, key); + sof_umutex_unlock(&dai->lock); if (ret < 0) return 0; @@ -260,12 +258,11 @@ int dai_get_fifo_depth(struct dai *dai, int direction) int dai_get_stream_id(struct dai *dai, int direction) { struct dai_properties props; - k_spinlock_key_t key; int ret; - key = k_spin_lock(&dai->lock); + sof_umutex_lock(&dai->lock, K_FOREVER); ret = dai_get_properties_copy(dai->dev, direction, 0, &props); - k_spin_unlock(&dai->lock, key); + sof_umutex_unlock(&dai->lock); if (ret < 0) return ret; @@ -275,12 +272,11 @@ int dai_get_stream_id(struct dai *dai, int direction) static int dai_get_fifo(struct dai *dai, int direction, int stream_id) { struct dai_properties props; - k_spinlock_key_t key; int ret; - key = k_spin_lock(&dai->lock); + sof_umutex_lock(&dai->lock, K_FOREVER); ret = dai_get_properties_copy(dai->dev, direction, stream_id, &props); - k_spin_unlock(&dai->lock, key); + sof_umutex_unlock(&dai->lock); if (ret < 0) return ret; @@ -512,6 +508,7 @@ __cold int dai_common_new(struct dai_data *dd, struct comp_dev *dev, const struct ipc_config_dai *dai_cfg) { uint32_t dir; + int ret; assert_can_be_cold(); @@ -535,7 +532,12 @@ __cold int dai_common_new(struct dai_data *dd, struct comp_dev *dev, return -ENODEV; } - k_spinlock_init(&dd->dai->lock); + ret = sof_umutex_init(&dd->dai->lock); + if (ret < 0) { + dai_put(dd->dai); + comp_err(dev, "sof_umutex_init() failed: %d", ret); + return ret; + } dma_sg_init(&dd->config.elem_array); dd->xrun = 0; @@ -647,6 +649,8 @@ __cold void dai_common_free(struct dai_data *dd) dai_release_llp_slot(dd); + sof_umutex_free(&dd->dai->lock); + dai_put(dd->dai); rfree(dd->dai_spec_config); @@ -1961,17 +1965,16 @@ uint32_t dai_get_init_delay_ms(struct dai *dai) { struct dai_properties props; uint32_t init_delay = 0; - k_spinlock_key_t key; int ret; if (!dai) return 0; - key = k_spin_lock(&dai->lock); + sof_umutex_lock(&dai->lock, K_FOREVER); ret = dai_get_properties_copy(dai->dev, 0, 0, &props); if (!ret) init_delay = props.reg_init_delay; - k_spin_unlock(&dai->lock, key); + sof_umutex_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 d25474c4816d..4674d4e858b6 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,7 +53,7 @@ struct dai { uint32_t dma_dev; const struct device *dev; const struct dai_data *dd; - struct k_spinlock lock; /* protect properties */ + struct sof_umutex lock; /* protect properties */ }; union hdalink_cfg { From 9545fd3f81d21bbaaf6c96eddb144b26695bd85a Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 21 May 2026 20:04:03 +0300 Subject: [PATCH 6/7] audio: component: reorder include order for lib/dai.h Reorder redefinitions to ensure "struct mod_alloc_ctx" is defined before lib/dai.h is included. Signed-off-by: Kai Vehmanen --- src/include/sof/audio/component.h | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/include/sof/audio/component.h b/src/include/sof/audio/component.h index 7fc9feb53736..fed83de58374 100644 --- a/src/include/sof/audio/component.h +++ b/src/include/sof/audio/component.h @@ -23,7 +23,6 @@ #include #include #include -#include #include #include #include @@ -37,6 +36,16 @@ struct dai_hw_params; struct timestamp_data; struct dai_ts_data; +struct k_heap; +struct vregion; +struct mod_alloc_ctx { + struct k_heap *heap; + struct vregion *vreg; +}; + +/* dai.h requires definition for mod_alloc_ctx */ +#include + /** \addtogroup component_api Component API * @{ */ @@ -579,13 +588,6 @@ struct comp_ops { uint64_t (*get_total_data_processed)(struct comp_dev *dev, uint32_t stream_no, bool input); }; -struct k_heap; -struct vregion; -struct mod_alloc_ctx { - struct k_heap *heap; - struct vregion *vreg; -}; - /** * Audio component base driver "class" * - used by all other component types. From 9fdefeb5a4d96ca99fe8f6ca7a63021107818a92 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Tue, 5 May 2026 12:39:26 +0300 Subject: [PATCH 7/7] 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 affect kernel space use. Signed-off-by: Kai Vehmanen --- src/audio/dai-zephyr.c | 50 ++++++++++++++++++++++---------- src/include/sof/lib/dai-zephyr.h | 3 ++ 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 a3b984d3ab5d..f7e0a0a2936b 100644 --- a/src/audio/dai-zephyr.c +++ b/src/audio/dai-zephyr.c @@ -23,6 +23,7 @@ #include #include #include +#include /* zephyr_ll_user_heap() */ #include #include #include @@ -542,6 +543,17 @@ __cold int dai_common_new(struct dai_data *dd, struct comp_dev *dev, dma_sg_init(&dd->config.elem_array); dd->xrun = 0; +#ifdef CONFIG_SOF_USERSPACE_LL + /* + * copier_dai_create() uses mod_zalloc() to allocate + * the 'dd' dai data object and does not set dd->alloc_ctx. + * If LL is run in user-space, assign the 'heap' here. + */ + dd->alloc_ctx.heap = zephyr_ll_user_heap(); +#else + dd->alloc_ctx.heap = NULL; +#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 */ @@ -594,6 +606,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(); @@ -606,10 +619,12 @@ __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)); + 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)); + comp_set_drvdata(dev, dd); ret = dai_common_new(dd, dev, dai_cfg); @@ -623,7 +638,7 @@ __cold static struct comp_dev *dai_new(const struct comp_driver *drv, return dev; error: - rfree(dd); + sof_heap_free(heap, dd); e_data: comp_free_device(dev); return NULL; @@ -653,7 +668,7 @@ __cold void dai_common_free(struct dai_data *dd) dai_put(dd->dai); - rfree(dd->dai_spec_config); + sof_heap_free(dd->alloc_ctx.heap, dd->dai_spec_config); } __cold static void dai_free(struct comp_dev *dev) @@ -667,7 +682,8 @@ __cold static void dai_free(struct comp_dev *dev) dai_common_free(dd); - rfree(dd); + /* heap is NULL to match what is passed in dai_new() */ + sof_heap_free(NULL, dd); comp_free_device(dev); } @@ -862,7 +878,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->alloc_ctx.heap, &config->elem_array, SOF_MEM_FLAG_USER, config->direction, period_count, period_bytes, @@ -888,8 +904,9 @@ static int dai_set_dma_config(struct dai_data *dd, struct comp_dev *dev) comp_dbg(dev, "entry"); - dma_cfg = rmalloc(SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT | SOF_MEM_FLAG_DMA, - sizeof(struct dma_config)); + dma_cfg = sof_heap_alloc(dd->alloc_ctx.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; @@ -918,10 +935,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->alloc_ctx.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->alloc_ctx.heap, dma_cfg); comp_err(dev, "dma_block_config allocation failed"); return -ENOMEM; } @@ -1055,7 +1073,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->alloc_ctx, buffer_size_preferred, buffer_size, SOF_MEM_FLAG_USER | SOF_MEM_FLAG_DMA, addr_align, BUFFER_USAGE_NOT_SHARED); if (!dd->dma_buffer) { @@ -1143,8 +1161,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->alloc_ctx.heap, &config->elem_array); + sof_heap_free(dd->alloc_ctx.heap, dd->z_config); dd->z_config = NULL; } @@ -1270,10 +1288,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->alloc_ctx.heap, &config->elem_array); if (dd->z_config) { - rfree(dd->z_config->head_block); - rfree(dd->z_config); + sof_heap_free(dd->alloc_ctx.heap, dd->z_config->head_block); + sof_heap_free(dd->alloc_ctx.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 4674d4e858b6..6a0845555eaa 100644 --- a/src/include/sof/lib/dai-zephyr.h +++ b/src/include/sof/lib/dai-zephyr.h @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -169,6 +170,8 @@ struct dai_data { #endif /* Copier gain params */ struct copier_gain_params *gain_data; + + struct mod_alloc_ctx alloc_ctx; }; /* 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 9724e7578c99..87eb594aa6a9 100644 --- a/src/ipc/ipc4/dai.c +++ b/src/ipc/ipc4/dai.c @@ -387,15 +387,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->alloc_ctx.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->alloc_ctx.heap, dd->dai_spec_config); dd->dai_spec_config = NULL; return -EINVAL; }