From 8ff1c692da027a7f077c5fd8e87a87a237e07b16 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Thu, 25 Jun 2026 22:14:43 +0300 Subject: [PATCH 1/4] module: generic: turn mod_data_blob_handler_new() into a syscall Convert mod_data_blob_handler_new() to a Zephyr syscall following the same pattern as mod_alloc_ext(), mod_free(), and mod_fast_get(). This allows modules running in user mode to call the function through the syscall interface. The z_vrfy_ handler validates the processing_module pointer and its heap region before dispatching to the implementation. Signed-off-by: Jyri Sarha --- src/audio/module_adapter/module/generic.c | 20 +++++++++++++++++-- .../sof/audio/module_adapter/module/generic.h | 7 ++++++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/audio/module_adapter/module/generic.c b/src/audio/module_adapter/module/generic.c index 95a5bb000d6a..3f280082c6a3 100644 --- a/src/audio/module_adapter/module/generic.c +++ b/src/audio/module_adapter/module/generic.c @@ -283,7 +283,7 @@ EXPORT_SYMBOL(z_impl_mod_alloc_ext); * Like comp_data_blob_handler_new() but the handler is automatically freed. */ #if CONFIG_COMP_BLOB -struct comp_data_blob_handler *mod_data_blob_handler_new(struct processing_module *mod) +struct comp_data_blob_handler *z_impl_mod_data_blob_handler_new(struct processing_module *mod) { struct module_resources * __maybe_unused res = &mod->priv.resources; struct comp_data_blob_handler *bhp; @@ -307,7 +307,7 @@ struct comp_data_blob_handler *mod_data_blob_handler_new(struct processing_modul return bhp; } -EXPORT_SYMBOL(mod_data_blob_handler_new); +EXPORT_SYMBOL(z_impl_mod_data_blob_handler_new); #endif /** @@ -476,6 +476,22 @@ int z_vrfy_mod_free(struct processing_module *mod, const void *ptr) return z_impl_mod_free(mod, ptr); } #include + +#if CONFIG_COMP_BLOB +struct comp_data_blob_handler *z_vrfy_mod_data_blob_handler_new(struct processing_module *mod) +{ + size_t h_size = 0; + uintptr_t h_start; + + K_OOPS(K_SYSCALL_MEMORY_WRITE(mod, sizeof(*mod))); + mod_heap_info(mod, &h_size, &h_start); + if (h_size) + K_OOPS(K_SYSCALL_MEMORY_WRITE(h_start, h_size)); + + return z_impl_mod_data_blob_handler_new(mod); +} +#include +#endif #endif #if CONFIG_COMP_BLOB diff --git a/src/include/sof/audio/module_adapter/module/generic.h b/src/include/sof/audio/module_adapter/module/generic.h index 94827d86cd9f..f4f4d6d381bc 100644 --- a/src/include/sof/audio/module_adapter/module/generic.h +++ b/src/include/sof/audio/module_adapter/module/generic.h @@ -241,7 +241,12 @@ static inline void *mod_zalloc(struct processing_module *mod, size_t size) } #if CONFIG_COMP_BLOB -struct comp_data_blob_handler *mod_data_blob_handler_new(struct processing_module *mod); +#if defined(__ZEPHYR__) && defined(CONFIG_SOF_FULL_ZEPHYR_APPLICATION) +__syscall struct comp_data_blob_handler *mod_data_blob_handler_new(struct processing_module *mod); +#else +struct comp_data_blob_handler *z_impl_mod_data_blob_handler_new(struct processing_module *mod); +#define mod_data_blob_handler_new z_impl_mod_data_blob_handler_new +#endif void mod_data_blob_handler_free(struct processing_module *mod, struct comp_data_blob_handler *dbh); #endif #if CONFIG_FAST_GET From bdcd011df808deabb0f432a44d8caa9e1594ab59 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Fri, 26 Jun 2026 11:30:10 +0300 Subject: [PATCH 2/4] module: generic: turn mod_balloc_align() into a syscall Convert mod_balloc_align() to a Zephyr syscall following the same pattern as mod_alloc_ext(), mod_free(), mod_fast_get(), and mod_data_blob_handler_new(). This allows modules running in user mode to allocate aligned buffer memory blocks through the syscall interface. The z_vrfy_ handler validates the processing_module pointer and its heap region before dispatching to the implementation. Signed-off-by: Jyri Sarha --- src/audio/module_adapter/module/generic.c | 18 ++++++++++++++++-- .../sof/audio/module_adapter/module/generic.h | 7 ++++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/audio/module_adapter/module/generic.c b/src/audio/module_adapter/module/generic.c index 3f280082c6a3..e1bd0813b097 100644 --- a/src/audio/module_adapter/module/generic.c +++ b/src/audio/module_adapter/module/generic.c @@ -185,7 +185,7 @@ void mod_heap_info(struct processing_module *mod, size_t *size, uintptr_t *start * unloaded. The back-end, rballoc(), always aligns the memory to * PLATFORM_DCACHE_ALIGN at the minimum. */ -void *mod_balloc_align(struct processing_module *mod, size_t size, size_t alignment) +void *z_impl_mod_balloc_align(struct processing_module *mod, size_t size, size_t alignment) { struct module_resources *res = &mod->priv.resources; struct module_resource *container; @@ -223,7 +223,7 @@ void *mod_balloc_align(struct processing_module *mod, size_t size, size_t alignm return ptr; } -EXPORT_SYMBOL(mod_balloc_align); +EXPORT_SYMBOL(z_impl_mod_balloc_align); /** * Allocates aligned memory block with flags for module. @@ -463,6 +463,20 @@ void *z_vrfy_mod_alloc_ext(struct processing_module *mod, uint32_t flags, size_t } #include +void *z_vrfy_mod_balloc_align(struct processing_module *mod, size_t size, size_t alignment) +{ + size_t h_size = 0; + uintptr_t h_start; + + K_OOPS(K_SYSCALL_MEMORY_WRITE(mod, sizeof(*mod))); + mod_heap_info(mod, &h_size, &h_start); + if (h_size) + K_OOPS(K_SYSCALL_MEMORY_WRITE(h_start, h_size)); + + return z_impl_mod_balloc_align(mod, size, alignment); +} +#include + int z_vrfy_mod_free(struct processing_module *mod, const void *ptr) { size_t h_size = 0; diff --git a/src/include/sof/audio/module_adapter/module/generic.h b/src/include/sof/audio/module_adapter/module/generic.h index f4f4d6d381bc..1d9602223563 100644 --- a/src/include/sof/audio/module_adapter/module/generic.h +++ b/src/include/sof/audio/module_adapter/module/generic.h @@ -191,7 +191,12 @@ struct module_processing_data { /*****************************************************************************/ int module_load_config(struct comp_dev *dev, const void *cfg, size_t size); int module_init(struct processing_module *mod); -void *mod_balloc_align(struct processing_module *mod, size_t size, size_t alignment); +#if defined(__ZEPHYR__) && defined(CONFIG_SOF_FULL_ZEPHYR_APPLICATION) +__syscall void *mod_balloc_align(struct processing_module *mod, size_t size, size_t alignment); +#else +void *z_impl_mod_balloc_align(struct processing_module *mod, size_t size, size_t alignment); +#define mod_balloc_align z_impl_mod_balloc_align +#endif void mod_resource_init(struct processing_module *mod); void mod_heap_info(struct processing_module *mod, size_t *size, uintptr_t *start); #if defined(__ZEPHYR__) && defined(CONFIG_SOF_FULL_ZEPHYR_APPLICATION) From 336aa1c1919308e7586c393e3c5a6a31a1dfce29 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Thu, 25 Jun 2026 22:26:20 +0300 Subject: [PATCH 3/4] module: generic: add mutex to protect module_resources Add a k_mutex to struct module_resources and take it around every objpool access in z_impl_mod_balloc_align(), z_impl_mod_alloc_ext(), z_impl_mod_data_blob_handler_new(), z_impl_mod_fast_get(), z_impl_mod_free(), and mod_free_all(). This prevents concurrent access to the resource tracking pool when a module's IPC thread and processing thread operate in parallel (e.g. set_large_config arriving while the module is running). In mod_free_all() the mutex is released before mod_resource_init() re-initializes it, so the lock is not held across re-initialization. Signed-off-by: Jyri Sarha --- src/audio/module_adapter/module/generic.c | 44 ++++++++++++++++--- .../sof/audio/module_adapter/module/generic.h | 2 + 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/src/audio/module_adapter/module/generic.c b/src/audio/module_adapter/module/generic.c index e1bd0813b097..83ac6eb46cae 100644 --- a/src/audio/module_adapter/module/generic.c +++ b/src/audio/module_adapter/module/generic.c @@ -12,6 +12,7 @@ */ #include +#include #include #include #include @@ -88,6 +89,7 @@ void mod_resource_init(struct processing_module *mod) struct module_resources *res = &mod->priv.resources; /* Init memory list */ + k_mutex_init(&res->lock); list_init(&res->objpool.list); res->objpool.heap = res->alloc->heap; res->objpool.vreg = res->alloc->vreg; @@ -192,13 +194,18 @@ void *z_impl_mod_balloc_align(struct processing_module *mod, size_t size, size_t MEM_API_CHECK_THREAD(res); + k_mutex_lock(&res->lock, K_FOREVER); + container = container_get(mod); - if (!container) + if (!container) { + k_mutex_unlock(&res->lock); return NULL; + } if (!size) { comp_err(mod->dev, "requested allocation of 0 bytes."); container_put(mod, container); + k_mutex_unlock(&res->lock); return NULL; } @@ -210,6 +217,7 @@ void *z_impl_mod_balloc_align(struct processing_module *mod, size_t size, size_t comp_err(mod->dev, "Failed to alloc %zu bytes %zu alignment for comp %#x.", size, alignment, dev_comp_id(mod->dev)); container_put(mod, container); + k_mutex_unlock(&res->lock); return NULL; } /* Store reference to allocated memory */ @@ -221,6 +229,7 @@ void *z_impl_mod_balloc_align(struct processing_module *mod, size_t size, size_t if (res->heap_usage > res->heap_high_water_mark) res->heap_high_water_mark = res->heap_usage; + k_mutex_unlock(&res->lock); return ptr; } EXPORT_SYMBOL(z_impl_mod_balloc_align); @@ -243,13 +252,18 @@ void *z_impl_mod_alloc_ext(struct processing_module *mod, uint32_t flags, size_t MEM_API_CHECK_THREAD(res); + k_mutex_lock(&res->lock, K_FOREVER); + container = container_get(mod); - if (!container) + if (!container) { + k_mutex_unlock(&res->lock); return NULL; + } if (!size) { comp_err(mod->dev, "requested allocation of 0 bytes."); container_put(mod, container); + k_mutex_unlock(&res->lock); return NULL; } @@ -260,6 +274,7 @@ void *z_impl_mod_alloc_ext(struct processing_module *mod, uint32_t flags, size_t comp_err(mod->dev, "Failed to alloc %zu bytes %zu alignment for comp %#x.", size, alignment, dev_comp_id(mod->dev)); container_put(mod, container); + k_mutex_unlock(&res->lock); return NULL; } /* Store reference to allocated memory */ @@ -271,6 +286,7 @@ void *z_impl_mod_alloc_ext(struct processing_module *mod, uint32_t flags, size_t if (res->heap_usage > res->heap_high_water_mark) res->heap_high_water_mark = res->heap_usage; + k_mutex_unlock(&res->lock); return ptr; } EXPORT_SYMBOL(z_impl_mod_alloc_ext); @@ -285,19 +301,24 @@ EXPORT_SYMBOL(z_impl_mod_alloc_ext); #if CONFIG_COMP_BLOB struct comp_data_blob_handler *z_impl_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; MEM_API_CHECK_THREAD(res); + k_mutex_lock(&res->lock, K_FOREVER); + container = container_get(mod); - if (!container) + if (!container) { + k_mutex_unlock(&res->lock); return NULL; + } bhp = comp_data_blob_handler_new_ext(mod->dev, false, NULL, NULL); if (!bhp) { container_put(mod, container); + k_mutex_unlock(&res->lock); return NULL; } @@ -305,6 +326,7 @@ struct comp_data_blob_handler *z_impl_mod_data_blob_handler_new(struct processin container->size = 0; container->type = MOD_RES_BLOB_HANDLER; + k_mutex_unlock(&res->lock); return bhp; } EXPORT_SYMBOL(z_impl_mod_data_blob_handler_new); @@ -327,13 +349,18 @@ const void *z_impl_mod_fast_get(struct processing_module *mod, const void * cons MEM_API_CHECK_THREAD(res); + k_mutex_lock(&res->lock, K_FOREVER); + container = container_get(mod); - if (!container) + if (!container) { + k_mutex_unlock(&res->lock); return NULL; + } ptr = fast_get(res->alloc, dram_ptr, size); if (!ptr) { container_put(mod, container); + k_mutex_unlock(&res->lock); return NULL; } @@ -341,6 +368,7 @@ const void *z_impl_mod_fast_get(struct processing_module *mod, const void * cons container->size = 0; container->type = MOD_RES_FAST_GET; + k_mutex_unlock(&res->lock); return ptr; } EXPORT_SYMBOL(z_impl_mod_fast_get); @@ -418,8 +446,12 @@ int z_impl_mod_free(struct processing_module *mod, const void *ptr) /* Find which container holds this memory */ struct mod_res_cb_arg cb_arg = {mod, ptr}; + + k_mutex_lock(&res->lock, K_FOREVER); int ret = objpool_iterate(&res->objpool, mod_res_free, &cb_arg); + k_mutex_unlock(&res->lock); + if (ret < 0) comp_err(mod->dev, "error: could not find memory pointed by %p", ptr); @@ -733,8 +765,10 @@ void mod_free_all(struct processing_module *mod) /* Free all contents found in used containers */ struct mod_res_cb_arg cb_arg = {mod, NULL}; + k_mutex_lock(&res->lock, K_FOREVER); objpool_iterate(&res->objpool, mod_res_free, &cb_arg); objpool_prune(&res->objpool); + k_mutex_unlock(&res->lock); /* Make sure resource lists and accounting are reset */ mod_resource_init(mod); diff --git a/src/include/sof/audio/module_adapter/module/generic.h b/src/include/sof/audio/module_adapter/module/generic.h index 1d9602223563..5c16c4c3669a 100644 --- a/src/include/sof/audio/module_adapter/module/generic.h +++ b/src/include/sof/audio/module_adapter/module/generic.h @@ -13,6 +13,7 @@ #ifndef __SOF_AUDIO_MODULE_GENERIC__ #define __SOF_AUDIO_MODULE_GENERIC__ +#include #include #include #include @@ -129,6 +130,7 @@ struct module_param { * when the module unloads. */ struct module_resources { + struct k_mutex lock; struct objpool_head objpool; size_t heap_usage; size_t heap_high_water_mark; From 1504a2c3f6b76e5f86a792b26ad11922ff3b4f70 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Thu, 25 Jun 2026 22:43:58 +0300 Subject: [PATCH 4/4] module: generic: remove MEM_API_CHECK_THREAD debug mechanism Remove the MEM_API_CHECK_THREAD() macro, CONFIG_MODULE_MEMORY_API_DEBUG Kconfig option, and the rsrc_mngr field from struct module_resources. This single-thread-owner check is now redundant: the previous commit added a spinlock that properly serializes all accesses to the resource pool, making the debug-only thread identity warning unnecessary. Signed-off-by: Jyri Sarha --- src/audio/module_adapter/Kconfig | 11 --------- src/audio/module_adapter/module/generic.c | 24 ------------------- .../sof/audio/module_adapter/module/generic.h | 7 ------ 3 files changed, 42 deletions(-) diff --git a/src/audio/module_adapter/Kconfig b/src/audio/module_adapter/Kconfig index a9f88dd1f2ad..9bea2a5dd175 100644 --- a/src/audio/module_adapter/Kconfig +++ b/src/audio/module_adapter/Kconfig @@ -13,17 +13,6 @@ menu "Processing modules" containers to allocate at once is selected by this config option. - config MODULE_MEMORY_API_DEBUG - bool "Turn on memory API thread safety checks" - default y if DEBUG - help - The Module Memory API structures are not protected - by locks. This is because the initialization, - allocation, and freeing of resources should always - be done in the same thread. This option adds an - assert to make sure no other thread makes such - operations. - config CADENCE_CODEC bool "Cadence codec" help diff --git a/src/audio/module_adapter/module/generic.c b/src/audio/module_adapter/module/generic.c index 83ac6eb46cae..ee1b2df92829 100644 --- a/src/audio/module_adapter/module/generic.c +++ b/src/audio/module_adapter/module/generic.c @@ -26,16 +26,6 @@ #include #endif -/* The __ZEPHYR__ condition is to keep cmocka tests working */ -#if CONFIG_MODULE_MEMORY_API_DEBUG && defined(__ZEPHYR__) -#define MEM_API_CHECK_THREAD(res) do { \ - if ((res)->rsrc_mngr != k_current_get()) \ - LOG_WRN("mngr %p != cur %p", (res)->rsrc_mngr, k_current_get()); \ -} while (0) -#else -#define MEM_API_CHECK_THREAD(res) -#endif - LOG_MODULE_DECLARE(module_adapter, CONFIG_SOF_LOG_LEVEL); int module_load_config(struct comp_dev *dev, const void *cfg, size_t size) @@ -124,9 +114,6 @@ int module_init(struct processing_module *mod) return -EIO; } -#if CONFIG_MODULE_MEMORY_API_DEBUG && defined(__ZEPHYR__) - mod->priv.resources.rsrc_mngr = k_current_get(); -#endif /* Now we can proceed with module specific initialization */ #if CONFIG_SOF_USERSPACE_APPLICATION if (mod->dev->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_DP) @@ -192,8 +179,6 @@ void *z_impl_mod_balloc_align(struct processing_module *mod, size_t size, size_t struct module_resources *res = &mod->priv.resources; struct module_resource *container; - MEM_API_CHECK_THREAD(res); - k_mutex_lock(&res->lock, K_FOREVER); container = container_get(mod); @@ -250,8 +235,6 @@ void *z_impl_mod_alloc_ext(struct processing_module *mod, uint32_t flags, size_t struct module_resources *res = &mod->priv.resources; struct module_resource *container; - MEM_API_CHECK_THREAD(res); - k_mutex_lock(&res->lock, K_FOREVER); container = container_get(mod); @@ -305,8 +288,6 @@ struct comp_data_blob_handler *z_impl_mod_data_blob_handler_new(struct processin struct comp_data_blob_handler *bhp; struct module_resource *container; - MEM_API_CHECK_THREAD(res); - k_mutex_lock(&res->lock, K_FOREVER); container = container_get(mod); @@ -347,8 +328,6 @@ const void *z_impl_mod_fast_get(struct processing_module *mod, const void * cons struct module_resource *container; const void *ptr; - MEM_API_CHECK_THREAD(res); - k_mutex_lock(&res->lock, K_FOREVER); container = container_get(mod); @@ -440,7 +419,6 @@ int z_impl_mod_free(struct processing_module *mod, const void *ptr) { struct module_resources *res = &mod->priv.resources; - MEM_API_CHECK_THREAD(res); if (!ptr) return 0; @@ -760,8 +738,6 @@ void mod_free_all(struct processing_module *mod) { struct module_resources *res = &mod->priv.resources; - MEM_API_CHECK_THREAD(res); - /* Free all contents found in used containers */ struct mod_res_cb_arg cb_arg = {mod, NULL}; diff --git a/src/include/sof/audio/module_adapter/module/generic.h b/src/include/sof/audio/module_adapter/module/generic.h index 5c16c4c3669a..c44b7a5a6c18 100644 --- a/src/include/sof/audio/module_adapter/module/generic.h +++ b/src/include/sof/audio/module_adapter/module/generic.h @@ -21,10 +21,6 @@ #include #include "module_interface.h" -/* The __ZEPHYR__ condition is to keep cmocka tests working */ -#if CONFIG_MODULE_MEMORY_API_DEBUG && defined(__ZEPHYR__) -#include -#endif #include /* @@ -135,9 +131,6 @@ struct module_resources { size_t heap_usage; size_t heap_high_water_mark; struct mod_alloc_ctx *alloc; -#if CONFIG_MODULE_MEMORY_API_DEBUG && defined(__ZEPHYR__) - k_tid_t rsrc_mngr; -#endif }; enum mod_resource_type {