From e93f93319eb6be6d35e54e49b2a41e9a087f8737 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Wed, 12 Aug 2026 12:42:25 +0300 Subject: [PATCH 1/3] module: generic: convert mod_free_all() to a syscall module_adapter_free() runs in a user-mode thread in userspace-LL mode and calls mod_free_all(). Unlike mod_free(), mod_free_all() was a plain function, so its internal sof_heap_free() calls on the module heap went through the user-mode syscall verifier, which only permits the LL user heap and K_OOPSes on the module heap. Convert mod_free_all() to a syscall so the objpool/heap cleanup runs in supervisor context, matching mod_free(). Signed-off-by: Kai Vehmanen --- src/audio/module_adapter/module/generic.c | 17 ++++++++++++++++- .../sof/audio/module_adapter/module/generic.h | 4 +++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/audio/module_adapter/module/generic.c b/src/audio/module_adapter/module/generic.c index ee1b2df92829..e14fe2b2cecc 100644 --- a/src/audio/module_adapter/module/generic.c +++ b/src/audio/module_adapter/module/generic.c @@ -501,6 +501,20 @@ int z_vrfy_mod_free(struct processing_module *mod, const void *ptr) } #include +void z_vrfy_mod_free_all(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)); + + z_impl_mod_free_all(mod); +} +#include + #if CONFIG_COMP_BLOB struct comp_data_blob_handler *z_vrfy_mod_data_blob_handler_new(struct processing_module *mod) { @@ -734,7 +748,7 @@ int module_reset(struct processing_module *mod) * * This function is called automatically when the module is unloaded. */ -void mod_free_all(struct processing_module *mod) +void z_impl_mod_free_all(struct processing_module *mod) { struct module_resources *res = &mod->priv.resources; @@ -749,6 +763,7 @@ void mod_free_all(struct processing_module *mod) /* Make sure resource lists and accounting are reset */ mod_resource_init(mod); } +EXPORT_SYMBOL(z_impl_mod_free_all); int module_free(struct processing_module *mod) { diff --git a/src/include/sof/audio/module_adapter/module/generic.h b/src/include/sof/audio/module_adapter/module/generic.h index 6740593a7cf1..6976b9d485ee 100644 --- a/src/include/sof/audio/module_adapter/module/generic.h +++ b/src/include/sof/audio/module_adapter/module/generic.h @@ -197,12 +197,15 @@ void mod_heap_info(struct processing_module *mod, size_t *size, uintptr_t *start __syscall void *mod_alloc_ext(struct processing_module *mod, uint32_t flags, size_t size, size_t alignment); __syscall int mod_free(struct processing_module *mod, const void *ptr); +__syscall void mod_free_all(struct processing_module *mod); #else void *z_impl_mod_alloc_ext(struct processing_module *mod, uint32_t flags, size_t size, size_t alignment); int z_impl_mod_free(struct processing_module *mod, const void *ptr); +void z_impl_mod_free_all(struct processing_module *mod); #define mod_alloc_ext z_impl_mod_alloc_ext #define mod_free z_impl_mod_free +#define mod_free_all z_impl_mod_free_all #endif /** @@ -259,7 +262,6 @@ const void *z_impl_mod_fast_get(struct processing_module *mod, const void * cons #endif void mod_fast_put(struct processing_module *mod, const void *sram_ptr); #endif -void mod_free_all(struct processing_module *mod); int module_prepare(struct processing_module *mod, struct sof_source **sources, int num_of_sources, struct sof_sink **sinks, int num_of_sinks); From ec22a69b2c8c45371227c2f82e65a9f3095f1b47 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Wed, 12 Aug 2026 19:02:42 +0300 Subject: [PATCH 2/3] posix: rtos: implement K_MUTEX_DEFINE() macro Implement a wrapper for this Zephyr interface so it can be used in generic SOF code. Signed-off-by: Kai Vehmanen --- posix/include/rtos/mutex.h | 1 + 1 file changed, 1 insertion(+) diff --git a/posix/include/rtos/mutex.h b/posix/include/rtos/mutex.h index 19b360bdaea5..f82b4169d73c 100644 --- a/posix/include/rtos/mutex.h +++ b/posix/include/rtos/mutex.h @@ -16,6 +16,7 @@ #include #define K_FOREVER ((k_timeout_t) { .ticks = 0xffffffff }) +#define K_MUTEX_DEFINE(name) struct k_mutex name struct k_mutex { struct k_spinlock lock; From 6b6af15e8f7bad2f10cefb4dc16294cfb0ea3a74 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Wed, 12 Aug 2026 15:29:13 +0300 Subject: [PATCH 3/3] module: generic: use a single static lock for module resources Replace the per-pool k_mutex embedded in struct module_resources with a single static K_MUTEX_DEFINE() shared by all module resource pools. This approach to implement locking works both in kernel and userspace SOF builds. When userspace is enabled, the resource API is only ever entered from supervisor context (the z_impl_* syscall bodies), so is it ok to have the lock only accessible from kernel. Lock contention is negligible: resource bookkeeping happens at module setup and teardown, not on the processing path, and the mutex carries priority inheritance for the rare overlap. Signed-off-by: Kai Vehmanen --- src/audio/module_adapter/module/generic.c | 55 +++++++++++-------- .../sof/audio/module_adapter/module/generic.h | 1 - 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/src/audio/module_adapter/module/generic.c b/src/audio/module_adapter/module/generic.c index e14fe2b2cecc..37071a2448e3 100644 --- a/src/audio/module_adapter/module/generic.c +++ b/src/audio/module_adapter/module/generic.c @@ -28,6 +28,15 @@ LOG_MODULE_DECLARE(module_adapter, CONFIG_SOF_LOG_LEVEL); +/* + * A single lock shared by all module resource pools. The resource API is only + * ever entered from supervisor context (the z_impl_* syscall bodies), so the + * lock does not need to live in the per-module, user-writable struct - a static + * kernel object serialises all pools. Contention is negligible because resource + * bookkeeping only happens at module setup/teardown, not on the processing path. + */ +static K_MUTEX_DEFINE(mod_res_lock); + int module_load_config(struct comp_dev *dev, const void *cfg, size_t size) { int ret; @@ -79,7 +88,6 @@ 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; @@ -179,18 +187,18 @@ 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; - k_mutex_lock(&res->lock, K_FOREVER); + k_mutex_lock(&mod_res_lock, K_FOREVER); container = container_get(mod); if (!container) { - k_mutex_unlock(&res->lock); + k_mutex_unlock(&mod_res_lock); return NULL; } if (!size) { comp_err(mod->dev, "requested allocation of 0 bytes."); container_put(mod, container); - k_mutex_unlock(&res->lock); + k_mutex_unlock(&mod_res_lock); return NULL; } @@ -202,7 +210,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); + k_mutex_unlock(&mod_res_lock); return NULL; } /* Store reference to allocated memory */ @@ -214,7 +222,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); + k_mutex_unlock(&mod_res_lock); return ptr; } EXPORT_SYMBOL(z_impl_mod_balloc_align); @@ -235,18 +243,18 @@ 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; - k_mutex_lock(&res->lock, K_FOREVER); + k_mutex_lock(&mod_res_lock, K_FOREVER); container = container_get(mod); if (!container) { - k_mutex_unlock(&res->lock); + k_mutex_unlock(&mod_res_lock); return NULL; } if (!size) { comp_err(mod->dev, "requested allocation of 0 bytes."); container_put(mod, container); - k_mutex_unlock(&res->lock); + k_mutex_unlock(&mod_res_lock); return NULL; } @@ -257,7 +265,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); + k_mutex_unlock(&mod_res_lock); return NULL; } /* Store reference to allocated memory */ @@ -269,7 +277,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); + k_mutex_unlock(&mod_res_lock); return ptr; } EXPORT_SYMBOL(z_impl_mod_alloc_ext); @@ -284,22 +292,21 @@ 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 *res = &mod->priv.resources; struct comp_data_blob_handler *bhp; struct module_resource *container; - k_mutex_lock(&res->lock, K_FOREVER); + k_mutex_lock(&mod_res_lock, K_FOREVER); container = container_get(mod); if (!container) { - k_mutex_unlock(&res->lock); + k_mutex_unlock(&mod_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); + k_mutex_unlock(&mod_res_lock); return NULL; } @@ -307,7 +314,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); + k_mutex_unlock(&mod_res_lock); return bhp; } EXPORT_SYMBOL(z_impl_mod_data_blob_handler_new); @@ -328,18 +335,18 @@ const void *z_impl_mod_fast_get(struct processing_module *mod, const void * cons struct module_resource *container; const void *ptr; - k_mutex_lock(&res->lock, K_FOREVER); + k_mutex_lock(&mod_res_lock, K_FOREVER); container = container_get(mod); if (!container) { - k_mutex_unlock(&res->lock); + k_mutex_unlock(&mod_res_lock); return NULL; } ptr = fast_get(res->alloc, dram_ptr, size); if (!ptr) { container_put(mod, container); - k_mutex_unlock(&res->lock); + k_mutex_unlock(&mod_res_lock); return NULL; } @@ -347,7 +354,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); + k_mutex_unlock(&mod_res_lock); return ptr; } EXPORT_SYMBOL(z_impl_mod_fast_get); @@ -425,10 +432,10 @@ 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); + k_mutex_lock(&mod_res_lock, K_FOREVER); int ret = objpool_iterate(&res->objpool, mod_res_free, &cb_arg); - k_mutex_unlock(&res->lock); + k_mutex_unlock(&mod_res_lock); if (ret < 0) comp_err(mod->dev, "error: could not find memory pointed by %p", ptr); @@ -755,10 +762,10 @@ void z_impl_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); + k_mutex_lock(&mod_res_lock, K_FOREVER); objpool_iterate(&res->objpool, mod_res_free, &cb_arg); objpool_prune(&res->objpool); - k_mutex_unlock(&res->lock); + k_mutex_unlock(&mod_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 6976b9d485ee..70fb602d1aca 100644 --- a/src/include/sof/audio/module_adapter/module/generic.h +++ b/src/include/sof/audio/module_adapter/module/generic.h @@ -125,7 +125,6 @@ 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;