From 99955c351f7061a9f236a133cfa461a8cfa1129d Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Thu, 25 Jun 2026 22:14:43 +0300 Subject: [PATCH 01/13] 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 fd0aa7143df94bdfb85ceb2943bf6115d5584c1e Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Fri, 26 Jun 2026 11:30:10 +0300 Subject: [PATCH 02/13] 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 7a823c01039012d7cb451bf6e3fa408e61c1aca4 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Thu, 25 Jun 2026 22:26:20 +0300 Subject: [PATCH 03/13] 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 c507a53dc7f61e8243fa1519740cdfc4abeb6716 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Thu, 25 Jun 2026 22:43:58 +0300 Subject: [PATCH 04/13] 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 { From f00243979e6415185a45678f83d12d9c582e97f2 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Thu, 5 Mar 2026 21:49:22 +0200 Subject: [PATCH 05/13] module-adapter: Move pipeline_comp_dp_task_init() after mod struct inits Move pipeline_comp_dp_task_init() call after module private data initializations so that the struct module_config is available already at pipeline_comp_dp_task_init() init time. Signed-off-by: Jyri Sarha --- src/audio/module_adapter/module_adapter.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/audio/module_adapter/module_adapter.c b/src/audio/module_adapter/module_adapter.c index 3da4079d757e..d62b8736e015 100644 --- a/src/audio/module_adapter/module_adapter.c +++ b/src/audio/module_adapter/module_adapter.c @@ -244,6 +244,17 @@ struct comp_dev *module_adapter_new_ext(const struct comp_driver *drv, struct comp_dev *dev = mod->dev; + dst = &mod->priv.cfg; + /* + * NOTE: dst->ext_data points to stack variable and contains + * pointers to IPC payload mailbox, so its only valid in + * functions that called from this function. This why + * the pointer is set NULL before this function exits. + */ +#if CONFIG_IPC_MAJOR_4 + dst->ext_data = &ext_data; +#endif + #if CONFIG_ZEPHYR_DP_SCHEDULER /* create a task for DP processing */ if (config->proc_domain == COMP_PROCESSING_DOMAIN_DP) { @@ -256,16 +267,6 @@ struct comp_dev *module_adapter_new_ext(const struct comp_driver *drv, } #endif /* CONFIG_ZEPHYR_DP_SCHEDULER */ - dst = &mod->priv.cfg; - /* - * NOTE: dst->ext_data points to stack variable and contains - * pointers to IPC payload mailbox, so its only valid in - * functions that called from this function. This why - * the pointer is set NULL before this function exits. - */ -#if CONFIG_IPC_MAJOR_4 - dst->ext_data = &ext_data; -#endif ret = module_adapter_init_data(dev, dst, config, &spec); if (ret) { comp_err(dev, "%d: module init data failed", From 69c37dd33dbc225abb92554929b79107066d5420 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Thu, 25 Jun 2026 15:40:32 +0300 Subject: [PATCH 06/13] module-adapter: module_adapter_dp_heap_new() remove heap_size parameter Remove unused heap_size parameter from module_adapter_dp_heap_new() function. Signed-off-by: Jyri Sarha --- src/audio/module_adapter/module_adapter.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/audio/module_adapter/module_adapter.c b/src/audio/module_adapter/module_adapter.c index d62b8736e015..8c5c1783f291 100644 --- a/src/audio/module_adapter/module_adapter.c +++ b/src/audio/module_adapter/module_adapter.c @@ -58,8 +58,7 @@ struct comp_dev *module_adapter_new(const struct comp_driver *drv, #define PAGE_SZ HOST_PAGE_SIZE #endif -static struct vregion *module_adapter_dp_heap_new(const struct comp_ipc_config *config, - size_t *heap_size) +static struct vregion *module_adapter_dp_heap_new(const struct comp_ipc_config *config) { /* src-lite with 8 channels has been seen allocating 14k in one go */ /* FIXME: the size will be derived from configuration */ @@ -84,11 +83,10 @@ static struct processing_module *module_adapter_mem_alloc(const struct comp_driv */ uint32_t flags = config->proc_domain == COMP_PROCESSING_DOMAIN_DP ? SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT : SOF_MEM_FLAG_USER; - size_t heap_size; if (config->proc_domain == COMP_PROCESSING_DOMAIN_DP && IS_ENABLED(CONFIG_SOF_VREGIONS) && IS_ENABLED(CONFIG_USERSPACE) && !IS_ENABLED(CONFIG_SOF_USERSPACE_USE_DRIVER_HEAP)) { - mod_vreg = module_adapter_dp_heap_new(config, &heap_size); + mod_vreg = module_adapter_dp_heap_new(config); if (!mod_vreg) { comp_cl_err(drv, "Failed to allocate DP module heap / vregion"); return NULL; @@ -101,7 +99,6 @@ static struct processing_module *module_adapter_mem_alloc(const struct comp_driv #else mod_heap = drv->user_heap; #endif - heap_size = 0; mod_vreg = NULL; } From aae02f9250a06487466b1691f9c82367eb49079a Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Thu, 5 Mar 2026 19:53:46 +0200 Subject: [PATCH 07/13] module-adapter: Size DP heap from IPC ext init data Use IPC module init extended data (the dp_data) to determine DP module heap size when available. Add Kconfig option SOF_USERSPACE_DP_DEFAULT_HEAP_SIZE (default 20480) as fallback when extended init data is not present or does not provide heap sizes. Sanity-check the requested sizes (reject values above 64 MB) and log the allocated heap size. Also pass ext_init through module_adapter_mem_alloc() to module_adapter_dp_heap_new() and fix a minor comment typo. Signed-off-by: Jyri Sarha --- src/audio/module_adapter/module_adapter.c | 47 ++++++++++++++++++----- zephyr/Kconfig | 9 +++++ 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/src/audio/module_adapter/module_adapter.c b/src/audio/module_adapter/module_adapter.c index 8c5c1783f291..835bc6ea86ee 100644 --- a/src/audio/module_adapter/module_adapter.c +++ b/src/audio/module_adapter/module_adapter.c @@ -58,17 +58,39 @@ struct comp_dev *module_adapter_new(const struct comp_driver *drv, #define PAGE_SZ HOST_PAGE_SIZE #endif -static struct vregion *module_adapter_dp_heap_new(const struct comp_ipc_config *config) +static struct vregion *module_adapter_dp_heap_new(const struct comp_ipc_config *config, + const struct module_ext_init_data *ext_init) { /* src-lite with 8 channels has been seen allocating 14k in one go */ - /* FIXME: the size will be derived from configuration */ - const size_t buf_size = 28 * 1024; + size_t buf_size = CONFIG_SOF_USERSPACE_DP_DEFAULT_HEAP_SIZE; +#if CONFIG_IPC_MAJOR_4 + if (config->ipc_extended_init && ext_init && ext_init->dp_data && + ext_init->dp_data->heap_bytes > 0) { + if (ext_init->dp_data->heap_bytes > MB(64)) { + LOG_ERR("Bad heap size %u bytes for %#x", + ext_init->dp_data->heap_bytes, config->id); + return NULL; + } + + buf_size = ext_init->dp_data->heap_bytes; + + LOG_INF("%zu byte heap size requested in IPC for %#x", buf_size, config->id); + } +#endif + /* + * A 1-to-1 replacement of the original heap implementation would be to + * have "lifetime size" equal to 0. But (1) this is invalid for + * vregion_create() and (2) we gradually move objects, that are simple + * to move to the lifetime buffer. Make it 4k for the beginning. + */ return vregion_create(buf_size); } -static struct processing_module *module_adapter_mem_alloc(const struct comp_driver *drv, - const struct comp_ipc_config *config) +static +struct processing_module *module_adapter_mem_alloc(const struct comp_driver *drv, + const struct comp_ipc_config *config, + const struct module_ext_init_data *ext_init) { struct k_heap *mod_heap; struct vregion *mod_vreg; @@ -86,7 +108,7 @@ static struct processing_module *module_adapter_mem_alloc(const struct comp_driv if (config->proc_domain == COMP_PROCESSING_DOMAIN_DP && IS_ENABLED(CONFIG_SOF_VREGIONS) && IS_ENABLED(CONFIG_USERSPACE) && !IS_ENABLED(CONFIG_SOF_USERSPACE_USE_DRIVER_HEAP)) { - mod_vreg = module_adapter_dp_heap_new(config); + mod_vreg = module_adapter_dp_heap_new(config, ext_init); if (!mod_vreg) { comp_cl_err(drv, "Failed to allocate DP module heap / vregion"); return NULL; @@ -227,8 +249,14 @@ struct comp_dev *module_adapter_new_ext(const struct comp_driver *drv, return NULL; } #endif + const struct module_ext_init_data *ext_init = +#if CONFIG_IPC_MAJOR_4 + &ext_data; +#else + NULL; +#endif - struct processing_module *mod = module_adapter_mem_alloc(drv, config); + struct processing_module *mod = module_adapter_mem_alloc(drv, config, ext_init); if (!mod) return NULL; @@ -245,8 +273,9 @@ struct comp_dev *module_adapter_new_ext(const struct comp_driver *drv, /* * NOTE: dst->ext_data points to stack variable and contains * pointers to IPC payload mailbox, so its only valid in - * functions that called from this function. This why - * the pointer is set NULL before this function exits. + * functions that are called from this function. This is + * why the pointer is set to NULL before this function + * exits. */ #if CONFIG_IPC_MAJOR_4 dst->ext_data = &ext_data; diff --git a/zephyr/Kconfig b/zephyr/Kconfig index 27f37d829b4e..5d2743fe56ae 100644 --- a/zephyr/Kconfig +++ b/zephyr/Kconfig @@ -69,6 +69,15 @@ config SOF_ZEPHYR_HEAP_SIZE NOTE: Keep in mind that the heap size should not be greater than the physical memory size of the system defined in DT (and this includes baseFW text/data). +config SOF_USERSPACE_DP_DEFAULT_HEAP_SIZE + int "Default heap size for DP userspace threads" + default 20480 + help + Defines the default heap size for userspace DP processing + threads. The value can be overridden with IPC module init + ext_init module payload. The default is derived from what is + required for SRC module to produce all supported conversions. + config SOF_USERSPACE_USE_SHARED_HEAP bool "Use shared heap for SOF userspace modules" depends on USERSPACE From 861c161acfca842d7708293b8e6b8df826ddd9a2 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Thu, 5 Mar 2026 21:35:41 +0200 Subject: [PATCH 08/13] pipeline: Use dp_data stack size for DP task Use IPC module init extended data (dp_data stack_bytes) to set the DP processing thread stack size when available. Fall back to TASK_DP_STACK_SIZE when ext init data is not present or does not provide a stack size. Add Kconfig option ZEPHYR_DP_SCHEDULER_MIN_STACK_SIZE (default 2048) to enforce a minimum stack size regardless of what the IPC payload requests. Signed-off-by: Jyri Sarha --- src/audio/pipeline/pipeline-schedule.c | 12 +++++++++++- zephyr/Kconfig | 9 +++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/audio/pipeline/pipeline-schedule.c b/src/audio/pipeline/pipeline-schedule.c index cb4ec8fd3c62..70ae1186b2a2 100644 --- a/src/audio/pipeline/pipeline-schedule.c +++ b/src/audio/pipeline/pipeline-schedule.c @@ -398,6 +398,7 @@ int pipeline_comp_dp_task_init(struct comp_dev *comp) { /* DP tasks are guaranteed to have a module_adapter */ struct processing_module *mod = comp_mod(comp); + size_t stack_size = TASK_DP_STACK_SIZE; struct task_ops ops = { .run = dp_task_run, .get_deadline = NULL, @@ -413,8 +414,17 @@ int pipeline_comp_dp_task_init(struct comp_dev *comp) unsigned int flags = IS_ENABLED(CONFIG_USERSPACE) ? K_USER : 0; #endif + if (mod->priv.cfg.ext_data && mod->priv.cfg.ext_data->dp_data && + mod->priv.cfg.ext_data->dp_data->stack_bytes > 0) { + stack_size = MAX(mod->priv.cfg.ext_data->dp_data->stack_bytes, + CONFIG_ZEPHYR_DP_SCHEDULER_MIN_STACK_SIZE); + comp_info(comp, "stack size set to %zu, %zu requested, min allowed %zu", + stack_size, mod->priv.cfg.ext_data->dp_data->stack_bytes, + CONFIG_ZEPHYR_DP_SCHEDULER_MIN_STACK_SIZE); + } + return scheduler_dp_task_init(&comp->task, SOF_UUID(dp_task_uuid), &ops, mod, - comp->ipc_config.core, TASK_DP_STACK_SIZE, flags); + comp->ipc_config.core, stack_size, flags); } #endif /* CONFIG_ZEPHYR_DP_SCHEDULER */ diff --git a/zephyr/Kconfig b/zephyr/Kconfig index 5d2743fe56ae..603e52aafebd 100644 --- a/zephyr/Kconfig +++ b/zephyr/Kconfig @@ -241,6 +241,15 @@ config ZEPHYR_DP_SCHEDULER DP modules can be located in dieffrent cores than LL pipeline modules, may have different tick (i.e. 300ms for speech reccognition, etc.) +config ZEPHYR_DP_SCHEDULER_MIN_STACK_SIZE + int "Minimum stack size for DP processing thread" + default 512 + help + Defines the minimum stack size allowed for DP processing + threads despite what is requested in the module init IPC + ext_init payload. If the stack size requested in the IPC is + smaller than this, then the value defined here takes over. + config CROSS_CORE_STREAM bool "Enable cross-core connected pipelines" default y if IPC_MAJOR_4 From ef3b4f05b4660f0d2a35827e2d4150a6e5cfbd3a Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Tue, 30 Jun 2026 19:19:01 +0300 Subject: [PATCH 09/13] module: Kconfig: add SOF_USERSPACE_DP_DEFAULT_HEAP_SIZE stub Add a dummy Kconfig definition for SOF_USERSPACE_DP_DEFAULT_HEAP_SIZE so the testbench build can resolve the symbol. The testbench does not use Zephyr, where this option is normally defined, so it needs a local fallback. Signed-off-by: Jyri Sarha --- src/audio/module_adapter/Kconfig | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/audio/module_adapter/Kconfig b/src/audio/module_adapter/Kconfig index 9bea2a5dd175..ee0110af6451 100644 --- a/src/audio/module_adapter/Kconfig +++ b/src/audio/module_adapter/Kconfig @@ -13,6 +13,16 @@ menu "Processing modules" containers to allocate at once is selected by this config option. + config SOF_USERSPACE_DP_DEFAULT_HEAP_SIZE + int "Default heap size for DP userspace threads" + default 20480 + help + Defines the default heap size for userspace DP processing + threads. The value can be overridden with IPC module init + ext_init module payload. The default is derived from what is + required for SRC module to produce all supported conversions. + (This is a dummy Kconfig option to help testbench to build.) + config CADENCE_CODEC bool "Cadence codec" help From ebbb8d9556c2be130662e6403d0a843226bc6eec Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Tue, 30 Jun 2026 20:05:59 +0300 Subject: [PATCH 10/13] posix: add Zephyr logging and util stubs for testbench build Add posix stub headers for and so that firmware source files using LOG_ERR, LOG_INF, LOG_WRN, LOG_DBG, KB, or MB can compile in the testbench / posix build without modification. Remove the #ifdef __ZEPHYR__ guard around the include in sof/trace/trace.h and the include in sof/common.h so the posix stubs are picked up automatically. The redundant LOG_MODULE_REGISTER / LOG_MODULE_DECLARE definitions in trace.h are removed since the posix stub now provides them. Remove the macros and fix couple of format string issues. Also the tools/logger CMakeList.txt requires some tuning to find the now unconditionally included zephyr/sys/util.h. Signed-off-by: Jyri Sarha --- posix/include/zephyr/logging/log.h | 33 ++++++++++++++++++++++++++++++ posix/include/zephyr/sys/util.h | 21 +++++++++++++++++++ src/include/sof/common.h | 2 -- src/include/sof/trace/trace.h | 10 +-------- tools/logger/CMakeLists.txt | 1 + zephyr/lib/fast-get.c | 9 +------- 6 files changed, 57 insertions(+), 19 deletions(-) create mode 100644 posix/include/zephyr/logging/log.h create mode 100644 posix/include/zephyr/sys/util.h diff --git a/posix/include/zephyr/logging/log.h b/posix/include/zephyr/logging/log.h new file mode 100644 index 000000000000..20ca6371bd63 --- /dev/null +++ b/posix/include/zephyr/logging/log.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright(c) 2026 Intel Corporation. All rights reserved. + * + * Stub for used by testbench / posix builds. + */ + +#ifndef __POSIX_ZEPHYR_LOGGING_LOG_H__ +#define __POSIX_ZEPHYR_LOGGING_LOG_H__ + +#include + +#ifndef LOG_ERR +#define LOG_ERR(fmt, ...) fprintf(stderr, "ERR: " fmt "\n", ##__VA_ARGS__) +#endif +#ifndef LOG_WRN +#define LOG_WRN(fmt, ...) fprintf(stderr, "WRN: " fmt "\n", ##__VA_ARGS__) +#endif +#ifndef LOG_INF +#define LOG_INF(fmt, ...) printf("INF: " fmt "\n", ##__VA_ARGS__) +#endif +#ifndef LOG_DBG +#define LOG_DBG(fmt, ...) do { } while (0) +#endif + +#ifndef LOG_MODULE_REGISTER +#define LOG_MODULE_REGISTER(ctx, level) +#endif +#ifndef LOG_MODULE_DECLARE +#define LOG_MODULE_DECLARE(ctx, level) +#endif + +#endif /* __POSIX_ZEPHYR_LOGGING_LOG_H__ */ diff --git a/posix/include/zephyr/sys/util.h b/posix/include/zephyr/sys/util.h new file mode 100644 index 000000000000..027b2ef15a6d --- /dev/null +++ b/posix/include/zephyr/sys/util.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright(c) 2026 Intel Corporation. All rights reserved. + * + * Stub for used by testbench / posix builds. + */ + +#ifndef __POSIX_ZEPHYR_SYS_UTIL_H__ +#define __POSIX_ZEPHYR_SYS_UTIL_H__ + +#include + +#ifndef KB +#define KB(x) (((size_t)(x)) << 10) +#endif + +#ifndef MB +#define MB(x) (KB(x) << 10) +#endif + +#endif /* __POSIX_ZEPHYR_SYS_UTIL_H__ */ diff --git a/src/include/sof/common.h b/src/include/sof/common.h index d7aa9a7989cb..bb193310e97f 100644 --- a/src/include/sof/common.h +++ b/src/include/sof/common.h @@ -14,9 +14,7 @@ /* callers must check/use the return value */ #define __must_check __attribute__((warn_unused_result)) -#ifdef __ZEPHYR__ #include -#endif /* Align the number to the nearest alignment value */ #ifndef IS_ALIGNED diff --git a/src/include/sof/trace/trace.h b/src/include/sof/trace/trace.h index fc3aa324f847..bd6a56d87da0 100644 --- a/src/include/sof/trace/trace.h +++ b/src/include/sof/trace/trace.h @@ -23,8 +23,8 @@ #ifdef __ZEPHYR__ #include -#include #endif +#include #if !CONFIG_LIBRARY #include @@ -155,12 +155,4 @@ struct tr_ctx { .level = default_log_level, \ } -/* Only define these two macros for XTOS to avoid the collision with - * zephyr/include/zephyr/logging/log.h - */ -#ifndef __ZEPHYR__ -#define LOG_MODULE_REGISTER(ctx, level) -#define LOG_MODULE_DECLARE(ctx, level) -#endif - #endif /* __SOF_TRACE_TRACE_H__ */ diff --git a/tools/logger/CMakeLists.txt b/tools/logger/CMakeLists.txt index ac83f5687454..8c15dd125739 100644 --- a/tools/logger/CMakeLists.txt +++ b/tools/logger/CMakeLists.txt @@ -38,6 +38,7 @@ target_compile_options(sof-logger PRIVATE target_include_directories(sof-logger PRIVATE "${SOF_ROOT_SOURCE_DIRECTORY}/src/include" + "${SOF_ROOT_SOURCE_DIRECTORY}/posix/include" "${SOF_ROOT_SOURCE_DIRECTORY}/tools/rimage/src/include" "${SOF_ROOT_SOURCE_DIRECTORY}" ) diff --git a/zephyr/lib/fast-get.c b/zephyr/lib/fast-get.c index cb0a2cfb07c7..f60efe48120c 100644 --- a/zephyr/lib/fast-get.c +++ b/zephyr/lib/fast-get.c @@ -19,14 +19,7 @@ #include #include -#ifdef __ZEPHYR__ #include -#else -#define LOG_DBG(...) do {} while (0) -#define LOG_INF(...) do {} while (0) -#define LOG_WRN(...) do {} while (0) -#define LOG_ERR(...) do {} while (0) -#endif struct sof_fast_get_entry { const void *dram_ptr; @@ -154,7 +147,7 @@ const void *fast_get(struct mod_alloc_ctx *alloc, const void *dram_ptr, size_t s if (entry->sram_ptr) { if (entry->size != size || entry->dram_ptr != dram_ptr) { - LOG_ERR("size %u != %u or ptr %p != %p mismatch", + LOG_ERR("size %zu != %zu or ptr %p != %p mismatch", entry->size, size, entry->dram_ptr, dram_ptr); ret = NULL; goto out; From d49ac90ca3de600010945072198db8853e71b8ff Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Wed, 27 May 2026 01:53:46 +0300 Subject: [PATCH 11/13] pipeline: allocate shared vregion for LL modules Create a per-pipeline vregion in pipeline_new() when the IPC4 pipeline extension payload specifies lifetime or interim heap sizes. The vregion size is the sum of both. Store the vregion pointer in struct pipeline. In module_adapter_new_ext() resolve the pipeline pointer before module_adapter_mem_alloc() so the pipeline's vregion can be passed down. LL modules on a pipeline with a vregion use it as their allocation backend via vregion_get(), instead of the driver's default heap. DP modules continue to create their own per-module vregion. Call vregion_set_interim() for the pipeline vregion in pipeline_complete() to switch the allocator to interim mode after all lifetime allocations are done. Release the pipeline vregion with vregion_put() in ipc_pipeline_free() before calling pipeline_free(). Warn if the refcount does not reach zero, indicating a module still holds a reference. Also fix a pre-existing leak in module_adapter_mem_free(): always free the per-module mod_alloc_ctx for LL modules regardless of the vregion refcount, since alloc is allocated from the system heap, not from the vregion. Signed-off-by: Jyri Sarha --- src/audio/module_adapter/module_adapter.c | 35 +++++++++++++++++------ src/audio/pipeline/pipeline-graph.c | 15 ++++++++++ src/include/sof/audio/pipeline.h | 2 ++ src/ipc/ipc4/helper.c | 5 ++++ 4 files changed, 49 insertions(+), 8 deletions(-) diff --git a/src/audio/module_adapter/module_adapter.c b/src/audio/module_adapter/module_adapter.c index 835bc6ea86ee..cf3ad90d3ff4 100644 --- a/src/audio/module_adapter/module_adapter.c +++ b/src/audio/module_adapter/module_adapter.c @@ -90,7 +90,8 @@ static struct vregion *module_adapter_dp_heap_new(const struct comp_ipc_config * static struct processing_module *module_adapter_mem_alloc(const struct comp_driver *drv, const struct comp_ipc_config *config, - const struct module_ext_init_data *ext_init) + const struct module_ext_init_data *ext_init, + struct vregion *ppl_vreg) { struct k_heap *mod_heap; struct vregion *mod_vreg; @@ -114,6 +115,10 @@ struct processing_module *module_adapter_mem_alloc(const struct comp_driver *drv return NULL; } mod_heap = NULL; + } else if (ppl_vreg && config->proc_domain == COMP_PROCESSING_DOMAIN_LL) { + mod_vreg = vregion_get(ppl_vreg); + mod_heap = NULL; + heap_size = 0; } else { #ifdef CONFIG_SOF_USERSPACE_LL mod_heap = sof_sys_user_heap_get(); @@ -198,10 +203,11 @@ static void module_adapter_mem_free(struct processing_module *mod) #endif if (alloc->vreg) { struct vregion *mod_vreg = alloc->vreg; + uint32_t proc_domain = mod->dev->ipc_config.proc_domain; vregion_free(mod_vreg, mod->dev); vregion_free(mod_vreg, mod); - if (!vregion_put(mod_vreg)) + if (!vregion_put(mod_vreg) || proc_domain == COMP_PROCESSING_DOMAIN_LL) sof_heap_free(alloc->heap, alloc); } else { sof_heap_free(mod_heap, mod->dev); @@ -256,7 +262,25 @@ struct comp_dev *module_adapter_new_ext(const struct comp_driver *drv, NULL; #endif - struct processing_module *mod = module_adapter_mem_alloc(drv, config, ext_init); +#if CONFIG_IPC_MAJOR_4 + struct ipc_comp_dev *ipc_pipe; + struct ipc *ipc = ipc_get(); + struct vregion *ppl_vreg = NULL; + + /* resolve the pipeline pointer early to pass its vregion to mem_alloc */ + ipc_pipe = ipc_get_comp_by_ppl_id(ipc, COMP_TYPE_PIPELINE, config->pipeline_id, + IPC_COMP_IGNORE_REMOTE); + if (ipc_pipe && ipc_pipe->pipeline) + ppl_vreg = ipc_pipe->pipeline->vreg; +#endif + + struct processing_module *mod = module_adapter_mem_alloc(drv, config, ext_init, +#if CONFIG_IPC_MAJOR_4 + ppl_vreg +#else + NULL +#endif + ); if (!mod) return NULL; @@ -315,12 +339,7 @@ struct comp_dev *module_adapter_new_ext(const struct comp_driver *drv, goto err; #if CONFIG_IPC_MAJOR_4 - struct ipc_comp_dev *ipc_pipe; - struct ipc *ipc = ipc_get(); - /* set the pipeline pointer if ipc_pipe is valid */ - ipc_pipe = ipc_get_comp_by_ppl_id(ipc, COMP_TYPE_PIPELINE, config->pipeline_id, - IPC_COMP_IGNORE_REMOTE); if (ipc_pipe) { dev->pipeline = ipc_pipe->pipeline; diff --git a/src/audio/pipeline/pipeline-graph.c b/src/audio/pipeline/pipeline-graph.c index 915987051275..5883565826c7 100644 --- a/src/audio/pipeline/pipeline-graph.c +++ b/src/audio/pipeline/pipeline-graph.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -135,6 +136,16 @@ struct pipeline *pipeline_new(struct k_heap *heap, uint32_t pipeline_id, uint32_ /* init pipeline */ p->heap = heap; + + /* Create vregion for pipeline and its modules if size info is available */ + if (IS_ENABLED(CONFIG_SOF_VREGIONS) && + pparams && pparams->mem_data && pparams->mem_data->heap_bytes) { + alloc->vreg = vregion_create(pparams->mem_data->heap_bytes); + if (!alloc->vreg) + pipe_cl_err("Failed to create pipeline vregion of %zu bytes, using heap", + pparams->mem_data->heap_bytes); + } + p->comp_id = comp_id; p->priority = priority; p->pipeline_id = pipeline_id; @@ -344,6 +355,10 @@ int pipeline_complete(struct pipeline *p, struct comp_dev *source, p->source_comp = source; p->sink_comp = sink; + + if (p->vreg) + vregion_set_interim(p->vreg); + p->status = COMP_STATE_READY; /* show heap status */ diff --git a/src/include/sof/audio/pipeline.h b/src/include/sof/audio/pipeline.h index 913a569c208c..e09920baf774 100644 --- a/src/include/sof/audio/pipeline.h +++ b/src/include/sof/audio/pipeline.h @@ -26,6 +26,7 @@ struct comp_dev; struct ipc; struct ipc_msg; struct k_heap; +struct vregion; /* * Pipeline status to stop execution of current path, but to keep the @@ -54,6 +55,7 @@ struct k_heap; */ struct pipeline { struct k_heap *heap; /**< heap used for allocating this pipeline */ + struct vregion *vreg; /**< shared vregion for pipeline modules */ uint32_t comp_id; /**< component id for pipeline */ uint32_t pipeline_id; /**< pipeline id */ uint32_t sched_id; /**< Scheduling component id */ diff --git a/src/ipc/ipc4/helper.c b/src/ipc/ipc4/helper.c index 25ba2a920532..8a7bf2291e24 100644 --- a/src/ipc/ipc4/helper.c +++ b/src/ipc/ipc4/helper.c @@ -513,6 +513,11 @@ __cold int ipc_pipeline_free(struct ipc *ipc, uint32_t comp_id) return ret; } + if (ipc_pipe->pipeline->vreg) { + if (vregion_put(ipc_pipe->pipeline->vreg)) + tr_warn(&ipc_tr, "pipeline vregion still in use"); + } + /* free buffer, delete all tasks and remove from list */ ret = pipeline_free(ipc_pipe->pipeline); if (ret < 0) { From 9b28d0b97fa2df207e8aa3428d0698329afd26db Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Thu, 28 May 2026 22:03:00 +0300 Subject: [PATCH 12/13] pipeline: add alloc context object to pipeline Replace the bare vregion pointer in struct pipeline with a mod_alloc_ctx object that bundles both the optional vregion and the heap pointer. The alloc context is always created in pipeline_new() and freed symmetrically in pipeline_free(), removing the separate cleanup that was in ipc_pipeline_free(). The pipeline object itself is now allocated through sof_ctx_zalloc() so it resides in the vregion when one is available, falling back to the default heap otherwise. LL modules share the pipeline's alloc context instead of only sharing the raw vregion pointer. A use_ppl_alloc flag gates the sharing to LL modules only, so DP modules continue to create their own vregion and alloc context as before. module_adapter_mem_free() detects whether a module's alloc belongs to its pipeline and either just releases the vregion reference (ppl_alloc case) or tears down the module's own alloc. Signed-off-by: Jyri Sarha --- src/audio/module_adapter/module_adapter.c | 89 ++++++++++++++--------- src/audio/pipeline/pipeline-graph.c | 41 ++++++++--- src/include/sof/audio/pipeline.h | 4 +- src/ipc/ipc4/helper.c | 5 -- 4 files changed, 86 insertions(+), 53 deletions(-) diff --git a/src/audio/module_adapter/module_adapter.c b/src/audio/module_adapter/module_adapter.c index cf3ad90d3ff4..3f66ab585cd5 100644 --- a/src/audio/module_adapter/module_adapter.c +++ b/src/audio/module_adapter/module_adapter.c @@ -91,7 +91,7 @@ static struct processing_module *module_adapter_mem_alloc(const struct comp_driver *drv, const struct comp_ipc_config *config, const struct module_ext_init_data *ext_init, - struct vregion *ppl_vreg) + struct mod_alloc_ctx *ppl_alloc) { struct k_heap *mod_heap; struct vregion *mod_vreg; @@ -106,6 +106,8 @@ struct processing_module *module_adapter_mem_alloc(const struct comp_driver *drv */ uint32_t flags = config->proc_domain == COMP_PROCESSING_DOMAIN_DP ? SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT : SOF_MEM_FLAG_USER; + bool use_ppl_alloc = ppl_alloc && + config->proc_domain == COMP_PROCESSING_DOMAIN_LL; if (config->proc_domain == COMP_PROCESSING_DOMAIN_DP && IS_ENABLED(CONFIG_SOF_VREGIONS) && IS_ENABLED(CONFIG_USERSPACE) && !IS_ENABLED(CONFIG_SOF_USERSPACE_USE_DRIVER_HEAP)) { @@ -115,10 +117,9 @@ struct processing_module *module_adapter_mem_alloc(const struct comp_driver *drv return NULL; } mod_heap = NULL; - } else if (ppl_vreg && config->proc_domain == COMP_PROCESSING_DOMAIN_LL) { - mod_vreg = vregion_get(ppl_vreg); - mod_heap = NULL; - heap_size = 0; + } else if (use_ppl_alloc) { + mod_vreg = ppl_alloc->vreg ? vregion_get(ppl_alloc->vreg) : NULL; + mod_heap = ppl_alloc->heap; } else { #ifdef CONFIG_SOF_USERSPACE_LL mod_heap = sof_sys_user_heap_get(); @@ -129,26 +130,38 @@ struct processing_module *module_adapter_mem_alloc(const struct comp_driver *drv mod_vreg = NULL; } - if (!mod_vreg) + if (use_ppl_alloc) { + /* LL modules use the pipeline's alloc context */ + mod = sof_ctx_alloc(ppl_alloc, flags, sizeof(*mod), 0); + } else if (!mod_vreg) { mod = sof_heap_alloc(mod_heap, flags, sizeof(*mod), 0); - else if (flags & SOF_MEM_FLAG_COHERENT) + } else if (flags & SOF_MEM_FLAG_COHERENT) { mod = vregion_alloc_coherent(mod_vreg, sizeof(*mod)); - else + } else { mod = vregion_alloc(mod_vreg, sizeof(*mod)); + } if (!mod) { comp_cl_err(drv, "failed to allocate memory for module"); goto emod; } - struct mod_alloc_ctx *alloc = sof_heap_alloc(mod_heap, flags, sizeof(*alloc), 0); + struct mod_alloc_ctx *alloc; - if (!alloc) - goto ealloc; + if (use_ppl_alloc) { + /* LL modules share the pipeline's alloc context */ + alloc = ppl_alloc; + } else { + alloc = sof_heap_alloc(mod_heap, flags, sizeof(*alloc), 0); + if (!alloc) + goto ealloc; + + memset(alloc, 0, sizeof(*alloc)); + alloc->heap = mod_heap; + alloc->vreg = mod_vreg; + } memset(mod, 0, sizeof(*mod)); - alloc->heap = mod_heap; - alloc->vreg = mod_vreg; mod->priv.resources.alloc = alloc; mod_resource_init(mod); @@ -158,7 +171,9 @@ struct processing_module *module_adapter_mem_alloc(const struct comp_driver *drv * then it can be cached. Effectively it can be only cached in * single-core configurations. */ - if (mod_vreg) + if (use_ppl_alloc) + dev = sof_ctx_alloc(ppl_alloc, SOF_MEM_FLAG_COHERENT, sizeof(*dev), 0); + else if (mod_vreg) dev = vregion_alloc_coherent(mod_vreg, sizeof(*dev)); else dev = sof_heap_alloc(mod_heap, SOF_MEM_FLAG_COHERENT, sizeof(*dev), 0); @@ -177,14 +192,20 @@ struct processing_module *module_adapter_mem_alloc(const struct comp_driver *drv return mod; edev: - sof_heap_free(mod_heap, alloc); + if (!use_ppl_alloc) + sof_heap_free(mod_heap, alloc); ealloc: - if (mod_vreg) + if (use_ppl_alloc) + sof_ctx_free(ppl_alloc, mod); + else if (mod_vreg) vregion_free(mod_vreg, mod); else sof_heap_free(mod_heap, mod); emod: - vregion_put(mod_vreg); + if (use_ppl_alloc) + vregion_put(ppl_alloc->vreg); + else + vregion_put(mod_vreg); return NULL; } @@ -192,27 +213,27 @@ struct processing_module *module_adapter_mem_alloc(const struct comp_driver *drv static void module_adapter_mem_free(struct processing_module *mod) { struct mod_alloc_ctx *alloc = mod->priv.resources.alloc; - struct k_heap *mod_heap = alloc->heap; + bool ppl_alloc = mod->dev->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_LL && + mod->dev->pipeline && mod->dev->pipeline->alloc == alloc; /* * In principle it shouldn't even be needed to free individual objects * on the module heap since we're freeing the heap itself too */ #if CONFIG_IPC_MAJOR_4 - sof_heap_free(mod_heap, mod->priv.cfg.input_pins); + sof_heap_free(alloc->heap, mod->priv.cfg.input_pins); #endif - if (alloc->vreg) { - struct vregion *mod_vreg = alloc->vreg; - uint32_t proc_domain = mod->dev->ipc_config.proc_domain; - - vregion_free(mod_vreg, mod->dev); - vregion_free(mod_vreg, mod); - if (!vregion_put(mod_vreg) || proc_domain == COMP_PROCESSING_DOMAIN_LL) - sof_heap_free(alloc->heap, alloc); + sof_ctx_free(alloc, mod->dev); + sof_ctx_free(alloc, mod); + + if (ppl_alloc) { + /* alloc belongs to pipeline, just release vregion reference */ + vregion_put(alloc->vreg); + } else if (alloc->vreg) { + if (!vregion_put(alloc->vreg)) + rfree(alloc); } else { - sof_heap_free(mod_heap, mod->dev); - sof_heap_free(mod_heap, mod); - sof_heap_free(mod_heap, alloc); + rfree(alloc); } } @@ -265,18 +286,18 @@ struct comp_dev *module_adapter_new_ext(const struct comp_driver *drv, #if CONFIG_IPC_MAJOR_4 struct ipc_comp_dev *ipc_pipe; struct ipc *ipc = ipc_get(); - struct vregion *ppl_vreg = NULL; + struct mod_alloc_ctx *ppl_alloc = NULL; - /* resolve the pipeline pointer early to pass its vregion to mem_alloc */ + /* resolve the pipeline pointer early to pass its alloc to mem_alloc */ ipc_pipe = ipc_get_comp_by_ppl_id(ipc, COMP_TYPE_PIPELINE, config->pipeline_id, IPC_COMP_IGNORE_REMOTE); if (ipc_pipe && ipc_pipe->pipeline) - ppl_vreg = ipc_pipe->pipeline->vreg; + ppl_alloc = ipc_pipe->pipeline->alloc; #endif struct processing_module *mod = module_adapter_mem_alloc(drv, config, ext_init, #if CONFIG_IPC_MAJOR_4 - ppl_vreg + ppl_alloc #else NULL #endif diff --git a/src/audio/pipeline/pipeline-graph.c b/src/audio/pipeline/pipeline-graph.c index 5883565826c7..9b834b31b18f 100644 --- a/src/audio/pipeline/pipeline-graph.c +++ b/src/audio/pipeline/pipeline-graph.c @@ -115,6 +115,7 @@ void pipeline_posn_init(struct sof *sof) struct pipeline *pipeline_new(struct k_heap *heap, uint32_t pipeline_id, uint32_t priority, uint32_t comp_id, struct create_pipeline_params *pparams) { + struct mod_alloc_ctx *alloc; struct sof_ipc_stream_posn posn; struct pipeline *p; int ret; @@ -125,17 +126,13 @@ struct pipeline *pipeline_new(struct k_heap *heap, uint32_t pipeline_id, uint32_ /* show heap status */ heap_trace_all(0); - /* allocate new pipeline */ - p = sof_heap_alloc(heap, SOF_MEM_FLAG_USER, sizeof(*p), 0); - if (!p) { - pipe_cl_err("Out of Memory"); + alloc = rzalloc(SOF_MEM_FLAG_USER, sizeof(*alloc)); + if (!alloc) { + pipe_cl_err("Failed to allocate pipeline alloc context"); return NULL; } - memset(p, 0, sizeof(*p)); - - /* init pipeline */ - p->heap = heap; + alloc->heap = heap; /* Create vregion for pipeline and its modules if size info is available */ if (IS_ENABLED(CONFIG_SOF_VREGIONS) && @@ -146,6 +143,16 @@ struct pipeline *pipeline_new(struct k_heap *heap, uint32_t pipeline_id, uint32_ pparams->mem_data->heap_bytes); } + p = sof_ctx_zalloc(alloc, SOF_MEM_FLAG_USER, sizeof(*p), 0); + if (!p) { + pipe_cl_err("Out of Memory"); + goto free_alloc; + } + + /* init pipeline */ + p->heap = heap; + p->alloc = alloc; + p->comp_id = comp_id; p->priority = priority; p->pipeline_id = pipeline_id; @@ -178,7 +185,10 @@ struct pipeline *pipeline_new(struct k_heap *heap, uint32_t pipeline_id, uint32_ return p; free: - sof_heap_free(heap, p); + sof_ctx_free(alloc, p); +free_alloc: + vregion_put(alloc->vreg); + rfree(alloc); return NULL; } @@ -258,6 +268,8 @@ void pipeline_disconnect(struct comp_dev *comp, struct comp_buffer *buffer, int /* pipelines must be inactive */ int pipeline_free(struct pipeline *p) { + struct mod_alloc_ctx *alloc = p->alloc; + pipe_dbg(p, "entry"); /* @@ -278,7 +290,12 @@ int pipeline_free(struct pipeline *p) pipeline_posn_offset_put(p->posn_offset); /* now free the pipeline */ - sof_heap_free(p->heap, p); + sof_ctx_free(alloc, p); + + /* free alloc context and vregion */ + if (vregion_put(alloc->vreg)) + pipe_cl_warn("pipeline vregion still in use"); + rfree(alloc); /* show heap status */ heap_trace_all(0); @@ -356,8 +373,8 @@ int pipeline_complete(struct pipeline *p, struct comp_dev *source, p->source_comp = source; p->sink_comp = sink; - if (p->vreg) - vregion_set_interim(p->vreg); + if (p->alloc && p->alloc->vreg) + vregion_set_interim(p->alloc->vreg); p->status = COMP_STATE_READY; diff --git a/src/include/sof/audio/pipeline.h b/src/include/sof/audio/pipeline.h index e09920baf774..3bd9d039a00d 100644 --- a/src/include/sof/audio/pipeline.h +++ b/src/include/sof/audio/pipeline.h @@ -26,7 +26,7 @@ struct comp_dev; struct ipc; struct ipc_msg; struct k_heap; -struct vregion; +struct mod_alloc_ctx; /* * Pipeline status to stop execution of current path, but to keep the @@ -55,7 +55,7 @@ struct vregion; */ struct pipeline { struct k_heap *heap; /**< heap used for allocating this pipeline */ - struct vregion *vreg; /**< shared vregion for pipeline modules */ + struct mod_alloc_ctx *alloc; /**< shared alloc context for pipeline modules */ uint32_t comp_id; /**< component id for pipeline */ uint32_t pipeline_id; /**< pipeline id */ uint32_t sched_id; /**< Scheduling component id */ diff --git a/src/ipc/ipc4/helper.c b/src/ipc/ipc4/helper.c index 8a7bf2291e24..25ba2a920532 100644 --- a/src/ipc/ipc4/helper.c +++ b/src/ipc/ipc4/helper.c @@ -513,11 +513,6 @@ __cold int ipc_pipeline_free(struct ipc *ipc, uint32_t comp_id) return ret; } - if (ipc_pipe->pipeline->vreg) { - if (vregion_put(ipc_pipe->pipeline->vreg)) - tr_warn(&ipc_tr, "pipeline vregion still in use"); - } - /* free buffer, delete all tasks and remove from list */ ret = pipeline_free(ipc_pipe->pipeline); if (ret < 0) { From f0bd0dbdf7ab6287365d212c4847b5b7f50176de Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Mon, 11 May 2026 22:17:53 +0300 Subject: [PATCH 13/13] audio: src: move filter and delay line allocation to init phase Refactor SRC and SRC-lite so that filter stage setup and delay line allocation happen during the module init callback instead of prepare. This ensures the bulk of SRC memory allocation occurs while the vregion allocator is still in its lifetime phase, before the interim heap is created. The allocations then persist across prepare/reset cycles without needing to be re-allocated each time. A new setup_stages() callback is added to struct comp_data, set by each variant (src.c, src_lite.c) to point at its own coefficient tables. The common src_allocate_delay_lines() is factored out of the old prepare path into src_common.c. For IPC4, src_init_stages() calls setup_stages() and src_allocate_delay_lines() at init time. The prepare path (src_prepare_do) only validates rates and sets downstream params. For IPC3, src_init_stages() is a no-op and src_prepare_do() retains the original behavior of doing full setup at prepare time, since IPC3 cannot be tested at this time. Signed-off-by: Jyri Sarha --- src/audio/src/src.c | 52 ++++++++++++++++--------- src/audio/src/src_common.c | 80 ++++++++++++++++++++++++++++++++++++++ src/audio/src/src_common.h | 5 +++ src/audio/src/src_ipc3.c | 24 ++++++++++++ src/audio/src/src_ipc4.c | 64 ++++++++++++++++++++++++++++++ src/audio/src/src_lite.c | 53 +++++++++++++++---------- 6 files changed, 240 insertions(+), 38 deletions(-) diff --git a/src/audio/src/src.c b/src/audio/src/src.c index 9870ad911ca8..b75ff5a4cce7 100644 --- a/src/audio/src/src.c +++ b/src/audio/src/src.c @@ -34,19 +34,16 @@ LOG_MODULE_DECLARE(src, CONFIG_SOF_LOG_LEVEL); -static int src_prepare(struct processing_module *mod, - struct sof_source **sources, int num_of_sources, - struct sof_sink **sinks, int num_of_sinks) +/* Set rate table pointers, compute rate indices, and copy filter stages. + * Must be in src.c because src_table1/2, src_in_fs, etc. come from + * the coefficient headers included by this file. + */ +static int src_setup_stages(struct processing_module *mod) { struct comp_data *cd = module_get_private_data(mod); struct src_param *a = &cd->param; int ret; - comp_info(mod->dev, "entry"); - - if (num_of_sources != 1 || num_of_sinks != 1) - return -EINVAL; - a->in_fs = src_in_fs; a->out_fs = src_out_fs; a->num_in_fs = NUM_IN_FS; @@ -54,27 +51,46 @@ static int src_prepare(struct processing_module *mod, a->max_fir_delay_size_xnch = (PLATFORM_MAX_CHANNELS * MAX_FIR_DELAY_SIZE); a->max_out_delay_size_xnch = (PLATFORM_MAX_CHANNELS * MAX_OUT_DELAY_SIZE); - src_get_source_sink_params(mod->dev, sources[0], sinks[0]); - ret = src_param_set(mod->dev, cd); if (ret < 0) return ret; - ret = src_allocate_copy_stages(mod, a, - src_table1[a->idx_out][a->idx_in], - src_table2[a->idx_out][a->idx_in]); - if (ret < 0) - return ret; + return src_allocate_copy_stages(mod, a, + src_table1[a->idx_out][a->idx_in], + src_table2[a->idx_out][a->idx_in]); +} - ret = src_params_general(mod, sources[0], sinks[0]); +static int src_do_init(struct processing_module *mod) +{ + struct comp_data *cd; + int ret; + + ret = src_init(mod); if (ret < 0) return ret; - return src_prepare_general(mod, sources[0], sinks[0]); + cd = module_get_private_data(mod); + cd->setup_stages = src_setup_stages; + + return src_init_stages(mod); +} + +static int src_prepare(struct processing_module *mod, + struct sof_source **sources, int num_of_sources, + struct sof_sink **sinks, int num_of_sinks) +{ + comp_info(mod->dev, "entry"); + + if (num_of_sources != 1 || num_of_sinks != 1) + return -EINVAL; + + src_get_source_sink_params(mod->dev, sources[0], sinks[0]); + + return src_prepare_do(mod, sources[0], sinks[0]); } static const struct module_interface src_interface = { - .init = src_init, + .init = src_do_init, .prepare = src_prepare, .process = src_process, .is_ready_to_process = src_is_ready_to_process, diff --git a/src/audio/src/src_common.c b/src/audio/src/src_common.c index 3a9c7dac280f..e4ccf76ae3ba 100644 --- a/src/audio/src/src_common.c +++ b/src/audio/src/src_common.c @@ -574,6 +574,86 @@ int src_params_general(struct processing_module *mod, return 0; } +/* Allocate delay lines and initialize the polyphase SRC filter. + * Assumes that cd->param rate table pointers (in_fs, out_fs, etc.) + * and stage pointers (stage1, stage2) are already set up via + * cd->setup_stages(). + */ +int src_allocate_delay_lines(struct processing_module *mod) +{ + struct comp_data *cd = module_get_private_data(mod); + struct comp_dev *dev = mod->dev; + size_t delay_lines_size; + int32_t *buffer_start; + int n; + int ret; + + /* For LL modules dev->period is already set from the pipeline. + * Compute dev->frames so buffer sizing works. + */ + if (!dev->frames) + component_set_nearest_period_frames(dev, cd->sink_rate); + + if (!cd->sink_rate) { + comp_err(dev, "zero sink rate"); + return -EINVAL; + } + + cd->source_frames = dev->frames * cd->source_rate / cd->sink_rate; + cd->sink_frames = dev->frames; + + /* Allocate needed memory for delay lines */ + ret = src_buffer_lengths(dev, cd, cd->channels_count); + if (ret < 0) { + comp_err(dev, "src_buffer_lengths() failed"); + return ret; + } + + delay_lines_size = ALIGN_UP(sizeof(int32_t) * cd->param.total, 8); + if (delay_lines_size == 0) { + comp_err(dev, "delay_lines_size = 0"); + return -EINVAL; + } + + mod_free(mod, cd->delay_lines); + + cd->delay_lines = mod_alloc(mod, delay_lines_size); + if (!cd->delay_lines) { + comp_err(dev, "failed to alloc cd->delay_lines, delay_lines_size = %zu", + delay_lines_size); + return -ENOMEM; + } + + memset(cd->delay_lines, 0, delay_lines_size); + buffer_start = cd->delay_lines + ALIGN_UP(cd->param.sbuf_length, 2); + + /* Initialize SRC for actual sample rate */ + n = src_polyphase_init(&cd->src, &cd->param, buffer_start); + + /* Reset stage buffer */ + cd->sbuf_r_ptr = cd->delay_lines; + cd->sbuf_w_ptr = cd->delay_lines; + cd->sbuf_avail = 0; + + switch (n) { + case 0: + cd->src_func = src_copy_sxx; + break; + case 1: + cd->src_func = src_1s; + break; + case 2: + cd->src_func = src_2s; + break; + default: + comp_info(dev, "missing coefficients for requested rates combination"); + cd->src_func = src_fallback; + return -EINVAL; + } + + return 0; +} + int src_param_set(struct comp_dev *dev, struct comp_data *cd) { struct src_param *a = &cd->param; diff --git a/src/audio/src/src_common.h b/src/audio/src/src_common.h index 98f29a263131..126b112737d3 100644 --- a/src/audio/src/src_common.h +++ b/src/audio/src/src_common.h @@ -167,6 +167,7 @@ struct comp_data { int (*src_func)(struct comp_data *cd, struct sof_source *source, struct sof_sink *sink); void (*polyphase_func)(struct src_stage_prm *s); + int (*setup_stages)(struct processing_module *mod); }; #if CONFIG_IPC_MAJOR_4 @@ -218,6 +219,7 @@ static inline int src_fallback(struct comp_data *cd, int src_allocate_copy_stages(struct processing_module *mod, struct src_param *prm, const struct src_stage *stage_src1, const struct src_stage *stage_src2); +int src_allocate_delay_lines(struct processing_module *mod); int src_rate_check(const void *spec); int src_set_params(struct processing_module *mod, struct sof_sink *sink); @@ -227,6 +229,9 @@ int src_prepare_general(struct processing_module *mod, struct sof_source *source, struct sof_sink *sink); int src_init(struct processing_module *mod); +int src_init_stages(struct processing_module *mod); +int src_prepare_do(struct processing_module *mod, + struct sof_source *source, struct sof_sink *sink); int src_copy_sxx(struct comp_data *cd, struct sof_source *source, struct sof_sink *sink); diff --git a/src/audio/src/src_ipc3.c b/src/audio/src/src_ipc3.c index 541efcdb24be..636c50bfdcea 100644 --- a/src/audio/src/src_ipc3.c +++ b/src/audio/src/src_ipc3.c @@ -195,3 +195,27 @@ int src_init(struct processing_module *mod) return 0; } +/* IPC3: No filter allocation at init, change ipc3 behavior as little as possible */ +int src_init_stages(struct processing_module *mod) +{ + return 0; +} + +/* IPC3: Full filter setup at prepare time */ +int src_prepare_do(struct processing_module *mod, + struct sof_source *source, struct sof_sink *sink) +{ + struct comp_data *cd = module_get_private_data(mod); + int ret; + + ret = cd->setup_stages(mod); + if (ret < 0) + return ret; + + ret = src_params_general(mod, source, sink); + if (ret < 0) + return ret; + + return src_prepare_general(mod, source, sink); +} + diff --git a/src/audio/src/src_ipc4.c b/src/audio/src/src_ipc4.c index 91f286347a5f..6e6f3c51b7be 100644 --- a/src/audio/src/src_ipc4.c +++ b/src/audio/src/src_ipc4.c @@ -246,3 +246,67 @@ __cold int src_init(struct processing_module *mod) return 0; } +/* Called after src_init() and setup_stages callback is set. + * Allocate filter stages and delay lines at init time. + */ +int src_init_stages(struct processing_module *mod) +{ + struct comp_data *cd = module_get_private_data(mod); + struct comp_dev *dev = mod->dev; + int ret; + + ret = cd->setup_stages(mod); + if (ret < 0) + return ret; + + /* For DP modules, dev->period is not yet set at init time (it's + * computed in src_set_params at prepare). Derive it here from the + * IPC config's output buffer size so that delay line allocation + * uses correct buffer sizes. + */ + if (dev->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_DP && !dev->frames) { + uint32_t frame_bytes = cd->channels_count * cd->sample_container_bytes; + + if (frame_bytes && cd->sink_rate) { + dev->period = 1000000ULL * + (cd->ipc_config.base.obs / frame_bytes) / + cd->sink_rate; + dev->period /= LL_TIMER_PERIOD_US; + dev->period *= LL_TIMER_PERIOD_US; + component_set_nearest_period_frames(dev, cd->sink_rate); + } + } + + return src_allocate_delay_lines(mod); +} + +/* At prepare time just verify rates and set downstream params */ +int src_prepare_do(struct processing_module *mod, + struct sof_source *source, struct sof_sink *sink) +{ + struct comp_data *cd = module_get_private_data(mod); + struct comp_dev *dev = mod->dev; + int ret; + + if (cd->source_rate != cd->ipc_config.base.audio_fmt.sampling_frequency || + cd->sink_rate != cd->ipc_config.sink_rate) { + comp_err(mod->dev, "rate mismatch: source %u/%u sink %u/%u", + cd->source_rate, + cd->ipc_config.base.audio_fmt.sampling_frequency, + cd->sink_rate, cd->ipc_config.sink_rate); + return -EINVAL; + } + + ret = src_set_params(mod, sink); + if (ret < 0) { + comp_err(mod->dev, "set params failed."); + return ret; + } + + /* Update frame counts with final dev->frames from src_set_params */ + cd->source_frames = dev->frames * cd->source_rate / cd->sink_rate; + cd->sink_frames = dev->frames; + + return src_prepare_general(mod, source, sink); +} + diff --git a/src/audio/src/src_lite.c b/src/audio/src/src_lite.c index 9d5593ff34ca..89d205fc7b85 100644 --- a/src/audio/src/src_lite.c +++ b/src/audio/src/src_lite.c @@ -14,24 +14,16 @@ LOG_MODULE_REGISTER(src_lite, CONFIG_SOF_LOG_LEVEL); -/* - * This function is 100% identical to src_prepare(), but it's - * assigning different coefficient arrays because it's including - * different headers. +/* Set rate table pointers, compute rate indices, and copy filter stages. + * Must be in src_lite.c because src_table1/2, src_in_fs, etc. come from + * the coefficient headers included by this file. */ -static int src_lite_prepare(struct processing_module *mod, - struct sof_source **sources, int num_of_sources, - struct sof_sink **sinks, int num_of_sinks) +static int src_lite_setup_stages(struct processing_module *mod) { struct comp_data *cd = module_get_private_data(mod); struct src_param *a = &cd->param; int ret; - comp_info(mod->dev, "entry"); - - if (num_of_sources != 1 || num_of_sinks != 1) - return -EINVAL; - a->in_fs = src_in_fs; a->out_fs = src_out_fs; a->num_in_fs = NUM_IN_FS; @@ -43,21 +35,42 @@ static int src_lite_prepare(struct processing_module *mod, if (ret < 0) return ret; - ret = src_allocate_copy_stages(mod, a, - src_table1[a->idx_out][a->idx_in], - src_table2[a->idx_out][a->idx_in]); - if (ret < 0) - return ret; + return src_allocate_copy_stages(mod, a, + src_table1[a->idx_out][a->idx_in], + src_table2[a->idx_out][a->idx_in]); +} + +static int src_lite_do_init(struct processing_module *mod) +{ + struct comp_data *cd; + int ret; - ret = src_params_general(mod, sources[0], sinks[0]); + ret = src_init(mod); if (ret < 0) return ret; - return src_prepare_general(mod, sources[0], sinks[0]); + cd = module_get_private_data(mod); + cd->setup_stages = src_lite_setup_stages; + + return src_init_stages(mod); +} + +static int src_lite_prepare(struct processing_module *mod, + struct sof_source **sources, int num_of_sources, + struct sof_sink **sinks, int num_of_sinks) +{ + comp_info(mod->dev, "entry"); + + if (num_of_sources != 1 || num_of_sinks != 1) + return -EINVAL; + + src_get_source_sink_params(mod->dev, sources[0], sinks[0]); + + return src_prepare_do(mod, sources[0], sinks[0]); } const struct module_interface src_lite_interface = { - .init = src_init, + .init = src_lite_do_init, .prepare = src_lite_prepare, .process = src_process, .is_ready_to_process = src_is_ready_to_process,