From f0e39906f7b72d1a2e0737b125a375e37d884387 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Thu, 5 Mar 2026 21:49:22 +0200 Subject: [PATCH 01/15] 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 adcb5ae829e0..b72c4cc5d30d 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 42ce2e188b97ae3a5c6d8363cd416578f51a6576 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Thu, 5 Mar 2026 19:53:46 +0200 Subject: [PATCH 02/15] module-adapter: Size DP heap from IPC ext init data Use IPC module init extended data (dp_data lifetime and interim heap sizes) 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 or below the dp_heap_user prefix) 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 | 60 +++++++++++++++++------ zephyr/Kconfig | 9 ++++ 2 files changed, 54 insertions(+), 15 deletions(-) diff --git a/src/audio/module_adapter/module_adapter.c b/src/audio/module_adapter/module_adapter.c index b72c4cc5d30d..4729d7a9ae15 100644 --- a/src/audio/module_adapter/module_adapter.c +++ b/src/audio/module_adapter/module_adapter.c @@ -59,23 +59,46 @@ struct comp_dev *module_adapter_new(const struct comp_driver *drv, #endif static struct vregion *module_adapter_dp_heap_new(const struct comp_ipc_config *config, + const struct module_ext_init_data *ext_init, size_t *heap_size) { /* 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; + size_t lifetime_size = 4096; - /* - * 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(4096, buf_size - 4096); +#if CONFIG_IPC_MAJOR_4 + if (config->ipc_extended_init && ext_init && ext_init->dp_data && + (ext_init->dp_data->lifetime_heap_bytes > 0 || + ext_init->dp_data->interim_heap_bytes > 0)) { + if (ext_init->dp_data->lifetime_heap_bytes > 64*1024*1024 || + ext_init->dp_data->interim_heap_bytes > 64*1024*1024 || + ext_init->dp_data->lifetime_heap_bytes == 0 || + ext_init->dp_data->interim_heap_bytes == 0) { + LOG_ERR("Bad lifetime %u or interim %u heap size for %#x", + ext_init->dp_data->lifetime_heap_bytes, + ext_init->dp_data->interim_heap_bytes, config->id); + return NULL; + } + + lifetime_size = ext_init->dp_data->lifetime_heap_bytes; + buf_size = ext_init->dp_data->lifetime_heap_bytes + + ext_init->dp_data->interim_heap_bytes; + + LOG_INF("to %u + %u = %zu byte heap size requested in IPC for %#x", + ext_init->dp_data->lifetime_heap_bytes, + ext_init->dp_data->interim_heap_bytes, buf_size, config->id); + } +#endif + + *heap_size = buf_size; + + return vregion_create(lifetime_size, buf_size - lifetime_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; @@ -94,7 +117,7 @@ static struct processing_module *module_adapter_mem_alloc(const struct comp_driv if (config->proc_domain == COMP_PROCESSING_DOMAIN_DP && 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, ext_init, &heap_size); if (!mod_vreg) { comp_cl_err(drv, "Failed to allocate DP module heap / vregion"); return NULL; @@ -230,8 +253,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; @@ -248,8 +277,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 f1d1896c4234..eb36d6b5bb76 100644 --- a/zephyr/Kconfig +++ b/zephyr/Kconfig @@ -61,6 +61,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 bc66be368c5238d4e1eeb0a1b9f6d670de6b6005 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Thu, 5 Mar 2026 21:35:41 +0200 Subject: [PATCH 03/15] 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 45fd1eed639c..5ad8a5074f0e 100644 --- a/src/audio/pipeline/pipeline-schedule.c +++ b/src/audio/pipeline/pipeline-schedule.c @@ -388,6 +388,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, @@ -403,8 +404,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 eb36d6b5bb76..555609a81dcc 100644 --- a/zephyr/Kconfig +++ b/zephyr/Kconfig @@ -214,6 +214,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 dcf96cf2a3aeea330f75aa25f4b78932c019d1d0 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Fri, 8 May 2026 00:05:29 +0300 Subject: [PATCH 04/15] schedule: dp: Set thread name with component ID Name DP threads using k_thread_name_set() with the component ID in hex. This makes it easier to identify DP threads in debug tools and Zephyr shell thread listings. Signed-off-by: Jyri Sarha --- src/schedule/zephyr_dp_schedule_application.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/schedule/zephyr_dp_schedule_application.c b/src/schedule/zephyr_dp_schedule_application.c index 06f2d7b46b12..e38bed8ff6a8 100644 --- a/src/schedule/zephyr_dp_schedule_application.c +++ b/src/schedule/zephyr_dp_schedule_application.c @@ -22,6 +22,7 @@ #include #include +#include #include "zephyr_dp_schedule.h" @@ -410,6 +411,15 @@ void scheduler_dp_internal_free(struct task *task) mod_free(pdata->mod, container_of(task, struct scheduler_dp_task_memory, task)); } +static void scheduler_dp_thread_name_set(k_tid_t thread_id, struct processing_module *mod) +{ + char name[CONFIG_THREAD_MAX_NAME_LEN]; + + snprintf(name, sizeof(name), "DP comp id %#x", mod->dev->ipc_config.id); + + k_thread_name_set(thread_id, name); +} + /* Called only in IPC context */ int scheduler_dp_task_init(struct task **task, const struct sof_uuid_entry *uid, const struct task_ops *ops, struct processing_module *mod, @@ -493,7 +503,7 @@ int scheduler_dp_task_init(struct task **task, const struct sof_uuid_entry *uid, pdata->thread_id = k_thread_create(pdata->thread, p_stack, stack_size, dp_thread_fn, ptask, NULL, NULL, CONFIG_DP_THREAD_PRIORITY, ptask->flags, K_FOREVER); - + scheduler_dp_thread_name_set(pdata->thread_id, mod); #ifdef CONFIG_SCHED_CPU_MASK /* pin the thread to specific core */ ret = k_thread_cpu_pin(pdata->thread_id, core); From 651f47645dfc49eb48f2c864ca628092540a8231 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Fri, 8 May 2026 00:07:29 +0300 Subject: [PATCH 05/15] topology2: Increase widget default value for stack from 4k to 8k The widget specific stack requirement works in such a way that all components in a pipeline have their stack requirements, and the pipeline stack size will be the highest of those values. I am not sure if this is actually the best method. I am not sure what factors affect the required stack size for a pipeline, but for now this will have to do. So 4k is not enough for all pipelines, and these generic numbers are here to guarantee that everything works until we have fine tuned values for all the modules. So increase the number to 8k to guarantee functionality. Signed-off-by: Jyri Sarha --- tools/topology/topology2/include/components/widget-common.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/topology/topology2/include/components/widget-common.conf b/tools/topology/topology2/include/components/widget-common.conf index 43ac5691c778..b6aef0ce77af 100644 --- a/tools/topology/topology2/include/components/widget-common.conf +++ b/tools/topology/topology2/include/components/widget-common.conf @@ -176,6 +176,6 @@ DefineAttribute."shared_bytes_requirement" { # These default values are here until we have measured module specific numbers stack_bytes_requirement 8192 -interim_heap_bytes_requirement 4096 +interim_heap_bytes_requirement 8192 lifetime_heap_bytes_requirement 16384 shared_bytes_requirement 4096 \ No newline at end of file From b054fdb0c67de352610d2c8667d18562075f3b75 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Fri, 8 May 2026 00:15:42 +0300 Subject: [PATCH 06/15] topology2: cavs-nocodec.conf: Fine tune SRC DP memory requirements Fine tune SRC DP memory requirements to values that should satisfy all use-cases, without using too much memory. For now the biggest memory consumers, the SRAM copy of the filter coefficients, and the filter memory, are not allocated in init phase, but in prepare. That is why the interim requirement is so high and lifetime requirement so low. This can be changed but it needs changes in SRC processing module code. Signed-off-by: Jyri Sarha --- tools/topology/topology2/cavs-nocodec.conf | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tools/topology/topology2/cavs-nocodec.conf b/tools/topology/topology2/cavs-nocodec.conf index fea906f98741..320a8e8ff02c 100644 --- a/tools/topology/topology2/cavs-nocodec.conf +++ b/tools/topology/topology2/cavs-nocodec.conf @@ -698,9 +698,11 @@ IncludeByKey.PASSTHROUGH { IncludeByKey.SRC_DOMAIN { "DP" { core_id $DP_SRC_CORE_ID - domain_id 123 - stack_bytes_requirement 4096 - heap_bytes_requirement 8192 + domain_id 0 + stack_bytes_requirement 2048 + interim_heap_bytes_requirement "$[(24 * 1024)]" + lifetime_heap_bytes_requirement 4096 + shared_bytes_requirement 0 } } } @@ -1382,9 +1384,11 @@ IncludeByKey.PASSTHROUGH { IncludeByKey.SRC_DOMAIN { "DP" { core_id $DP_SRC_CORE_ID - domain_id 123 - stack_bytes_requirement 4096 - heap_bytes_requirement 8192 + domain_id 0 + stack_bytes_requirement 2048 + interim_heap_bytes_requirement "$[(24 * 1024)]" + lifetime_heap_bytes_requirement 4096 + shared_bytes_requirement 0 } } From 98da05f76666fd832e50746005de6b5ea4653338 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Tue, 12 May 2026 16:42:01 +0300 Subject: [PATCH 07/15] audio: ring_buffer: allocate from lifetime vregion partition Ring buffers at LL/DP boundaries are created during pipeline_connect and persist until module free. They are never freed and reallocated across prepare/reset cycles, so they belong in the lifetime partition rather than interim. This allows DP modules to use a smaller interim partition since the ring buffer struct and data buffer no longer consume interim space. Signed-off-by: Jyri Sarha --- src/audio/buffers/ring_buffer.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/audio/buffers/ring_buffer.c b/src/audio/buffers/ring_buffer.c index fe67027df8db..a9b1d6161c36 100644 --- a/src/audio/buffers/ring_buffer.c +++ b/src/audio/buffers/ring_buffer.c @@ -304,9 +304,9 @@ struct ring_buffer *ring_buffer_create(struct comp_dev *dev, size_t min_availabl if (!vreg) ring_buffer = sof_heap_alloc(heap, memory_flags, sizeof(*ring_buffer), 0); else if (is_shared) - ring_buffer = vregion_alloc_coherent(vreg, VREGION_MEM_TYPE_INTERIM, sizeof(*ring_buffer)); + ring_buffer = vregion_alloc_coherent(vreg, VREGION_MEM_TYPE_LIFETIME, sizeof(*ring_buffer)); else - ring_buffer = vregion_alloc(vreg, VREGION_MEM_TYPE_INTERIM, sizeof(*ring_buffer)); + ring_buffer = vregion_alloc(vreg, VREGION_MEM_TYPE_LIFETIME, sizeof(*ring_buffer)); if (!ring_buffer) return NULL; @@ -383,7 +383,7 @@ struct ring_buffer *ring_buffer_create(struct comp_dev *dev, size_t min_availabl void *data_buf; if (vreg) - data_buf = vregion_alloc_align(vreg, VREGION_MEM_TYPE_INTERIM, ring_buffer->data_buffer_size, + data_buf = vregion_alloc_align(vreg, VREGION_MEM_TYPE_LIFETIME, ring_buffer->data_buffer_size, PLATFORM_DCACHE_ALIGN); else data_buf = sof_heap_alloc(heap, user_get_buffer_memory_region(dev->drv), From 457325162fe280dd879dfc7ee7bedfddaf7f39f7 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Tue, 12 May 2026 19:31:41 +0300 Subject: [PATCH 08/15] lib: vregion: lazy interim heap creation from remaining lifetime space Refactor the vregion memory layout to use a single contiguous buffer instead of two separately page-aligned partitions. The vregion struct is placed at the base, followed by lifetime allocations growing upward. The interim k_heap is created lazily on the first interim allocation from whatever space remains after lifetime allocations. This eliminates the rigid partition boundary that previously wasted memory when lifetime usage was smaller or larger than pre-configured. The interim heap creation is deferred until actually needed, at which point the lifetime region is sealed and any further lifetime allocation requests are redirected to the interim heap with a warning. Key changes: - vregion_create(): Sum lifetime+interim into one buffer, no k_heap init at creation time - New interim_heap_init(): Called lazily, page-aligns interim start, logs lifetime used and interim available at INFO level - lifetime_alloc(): Falls back to interim with warning if called after interim is initialized - interim_alloc(): Triggers interim_heap_init() on first call - vregion_free(): Guards interim pointer range check with interim_initialized flag Signed-off-by: Jyri Sarha --- zephyr/lib/vregion.c | 100 +++++++++++++++++++++++++++++-------------- 1 file changed, 69 insertions(+), 31 deletions(-) diff --git a/zephyr/lib/vregion.c b/zephyr/lib/vregion.c index 84af0d0645e6..b23dd5fd5cb6 100644 --- a/zephyr/lib/vregion.c +++ b/zephyr/lib/vregion.c @@ -87,8 +87,9 @@ struct vregion { struct k_mutex lock; /* protect vregion heaps and use-count */ unsigned int use_count; - /* interim heap */ + /* interim heap - created lazily on first interim allocation */ struct interim_heap interim; /* interim heap */ + bool interim_initialized; /* true after k_heap_init for interim */ /* lifetime heap */ struct vlinear_heap lifetime; /* lifetime linear heap */ @@ -118,13 +119,13 @@ struct vregion *vregion_create(size_t lifetime_size, size_t interim_size) } /* - * Align up lifetime sizes and interim sizes to nearest page, the - * vregion structure is stored in lifetime area so account for its size too. + * Sum lifetime and interim together into one contiguous buffer. + * The vregion struct is stored at the start, followed by lifetime + * allocations growing upward. The interim k_heap is created lazily + * from the remaining space on the first interim allocation. */ - lifetime_size += sizeof(*vr); - lifetime_size = ALIGN_UP(lifetime_size, CONFIG_MM_DRV_PAGE_SIZE); - interim_size = ALIGN_UP(interim_size, CONFIG_MM_DRV_PAGE_SIZE); - total_size = lifetime_size + interim_size; + total_size = lifetime_size + interim_size + sizeof(*vr); + total_size = ALIGN_UP(total_size, CONFIG_MM_DRV_PAGE_SIZE); /* allocate pages for vregion */ pages = total_size / CONFIG_MM_DRV_PAGE_SIZE; @@ -132,27 +133,21 @@ struct vregion *vregion_create(size_t lifetime_size, size_t interim_size) if (!vregion_base) return NULL; - /* init vregion - place it at the start of the lifetime region */ - vr = (struct vregion *)(vregion_base + interim_size); + /* init vregion - place it at the start of the buffer */ + vr = (struct vregion *)vregion_base; vr->base = vregion_base; vr->size = total_size; vr->pages = pages; - /* set partition sizes */ - vr->interim.heap.heap.init_bytes = interim_size; - vr->lifetime.size = lifetime_size; - - /* set base addresses for partitions */ - vr->interim.heap.heap.init_mem = vr->base; - vr->lifetime.base = vr->base + interim_size; - - /* set alloc ptr addresses for lifetime linear partitions */ - vr->lifetime.ptr = vr->lifetime.base + - ALIGN_UP(sizeof(*vr), CONFIG_DCACHE_LINE_SIZE); /* skip vregion struct */ + /* lifetime linear allocator starts right after the vregion struct */ + vr->lifetime.base = vregion_base; + vr->lifetime.size = total_size; + vr->lifetime.ptr = vregion_base + + ALIGN_UP(sizeof(*vr), CONFIG_DCACHE_LINE_SIZE); vr->lifetime.used = ALIGN_UP(sizeof(*vr), CONFIG_DCACHE_LINE_SIZE); - /* init interim heaps */ - k_heap_init(&vr->interim.heap, vr->interim.heap.heap.init_mem, interim_size); + /* interim heap is not initialized yet - will be created lazily */ + vr->interim_initialized = false; k_mutex_init(&vr->lock); /* The creator is the first user */ @@ -161,8 +156,6 @@ struct vregion *vregion_create(size_t lifetime_size, size_t interim_size) /* log the new vregion */ LOG_INF("new at base %p size %#zx pages %u struct embedded at %p", (void *)vr->base, total_size, pages, (void *)vr); - LOG_DBG(" interim size %#zx at %p", interim_size, (void *)vr->interim.heap.heap.init_mem); - LOG_DBG(" lifetime size %#zx at %p", lifetime_size, (void *)vr->lifetime.base); return vr; } @@ -209,19 +202,53 @@ struct vregion *vregion_put(struct vregion *vr) return NULL; } +/** + * @brief Initialize the interim heap from remaining vregion space. + * + * Called on first interim allocation. Creates the k_heap from all buffer + * space not yet consumed by lifetime allocations. + * + * @param[in] vr Pointer to the virtual region instance. + */ +static void interim_heap_init(struct vregion *vr) +{ + uint8_t *interim_base; + size_t interim_size; + + /* interim heap starts right after current lifetime pointer, page-aligned */ + interim_base = UINT_TO_POINTER(ALIGN_UP(POINTER_TO_UINT(vr->lifetime.ptr), + CONFIG_MM_DRV_PAGE_SIZE)); + interim_size = (vr->base + vr->size) - interim_base; + + LOG_INF("creating interim heap: lifetime used %zu, interim available %zu at %p", + vr->lifetime.used, interim_size, (void *)interim_base); + + /* cap lifetime so no more lifetime allocs can grow into interim */ + vr->lifetime.size = interim_base - vr->base; + + vr->interim.heap.heap.init_mem = interim_base; + vr->interim.heap.heap.init_bytes = interim_size; + k_heap_init(&vr->interim.heap, interim_base, interim_size); + vr->interim_initialized = true; +} + /** * @brief Allocate memory with alignment from the virtual region dynamic heap. * + * @param[in] vr Pointer to the virtual region instance. * @param[in] heap Pointer to the heap to use. * @param[in] size Size of the allocation. * @param[in] align Alignment of the allocation. * @return void* Pointer to the allocated memory, or NULL on failure. */ -static void *interim_alloc(struct interim_heap *heap, +static void *interim_alloc(struct vregion *vr, struct interim_heap *heap, size_t size, size_t align) { void *ptr; + if (!vr->interim_initialized) + interim_heap_init(vr); + ptr = k_heap_aligned_alloc(&heap->heap, align, size, K_NO_WAIT); if (!ptr) LOG_WRN("interim alloc failed for %d bytes align %d", @@ -244,19 +271,29 @@ static void interim_free(struct interim_heap *heap, void *ptr) /** * @brief Allocate memory from the virtual region lifetime allocator. * + * If the interim heap has already been created (i.e., an interim allocation + * was made), log a warning and fall back to interim allocation. + * + * @param[in] vr Pointer to the virtual region instance. * @param[in] heap Pointer to the linear heap to use. * @param[in] size Size of the allocation. * @param[in] align Alignment of the allocation. * * @return void* Pointer to the allocated memory, or NULL on failure. */ -static void *lifetime_alloc(struct vlinear_heap *heap, +static void *lifetime_alloc(struct vregion *vr, struct vlinear_heap *heap, size_t size, size_t align) { void *ptr; uint8_t *aligned_ptr; size_t heap_obj_size; + /* If interim heap already exists, lifetime partition is sealed */ + if (vr->interim_initialized) { + LOG_WRN("lifetime alloc after interim created, using interim for %zu bytes", size); + return interim_alloc(vr, &vr->interim, size, align); + } + /* align heap pointer to alignment requested */ aligned_ptr = UINT_TO_POINTER(ALIGN_UP(POINTER_TO_UINT(heap->ptr), align)); @@ -312,14 +349,15 @@ void vregion_free(struct vregion *vr, void *ptr) if (sys_cache_is_ptr_uncached(ptr)) ptr = sys_cache_cached_ptr_get(ptr); - if (ptr >= (void *)vr->interim.heap.heap.init_mem && + /* Check if pointer is in interim heap (if initialized) */ + if (vr->interim_initialized && + ptr >= (void *)vr->interim.heap.heap.init_mem && ptr < (void *)((uint8_t *)vr->interim.heap.heap.init_mem + vr->interim.heap.heap.init_bytes)) - /* pointer is in interim heap */ interim_free(&vr->interim, ptr); else if (ptr >= (void *)vr->lifetime.base && ptr < (void *)(vr->lifetime.base + vr->lifetime.size)) - /* pointer is in lifetime heap */ + /* pointer is in lifetime area - no-op free */ lifetime_free(&vr->lifetime, ptr); else LOG_ERR("error: vregion free invalid pointer %p", ptr); @@ -353,10 +391,10 @@ void *vregion_alloc_align(struct vregion *vr, enum vregion_mem_type type, switch (type) { case VREGION_MEM_TYPE_INTERIM: - p = interim_alloc(&vr->interim, size, alignment); + p = interim_alloc(vr, &vr->interim, size, alignment); break; case VREGION_MEM_TYPE_LIFETIME: - p = lifetime_alloc(&vr->lifetime, size, alignment); + p = lifetime_alloc(vr, &vr->lifetime, size, alignment); break; default: LOG_ERR("error: invalid memory type %d", type); From d46b9c6048ad32643d71f5a71a587d2596acb6de Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Tue, 12 May 2026 20:19:20 +0300 Subject: [PATCH 09/15] lib: vregion: add VREGION_MEM_TYPE_INDIFFERENT allocation type Add a third memory type that uses lifetime before the interim heap is initialized and interim after, without triggering interim heap creation or logging a warning. This is suitable for small metadata allocations that do not care which partition they reside in. Signed-off-by: Jyri Sarha --- src/include/sof/lib/vregion.h | 1 + zephyr/lib/vregion.c | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/src/include/sof/lib/vregion.h b/src/include/sof/lib/vregion.h index 612443f5bc48..a45f696a5c0e 100644 --- a/src/include/sof/lib/vregion.h +++ b/src/include/sof/lib/vregion.h @@ -22,6 +22,7 @@ struct vregion; * - lifetime: allocation that cannot be freed i.e. init data, pipeline data. */ enum vregion_mem_type { + VREGION_MEM_TYPE_INDIFFERENT, /* lifetime before interim init, interim after */ VREGION_MEM_TYPE_INTERIM, /* interim allocation that can be freed */ VREGION_MEM_TYPE_LIFETIME, /* lifetime allocation */ }; diff --git a/zephyr/lib/vregion.c b/zephyr/lib/vregion.c index b23dd5fd5cb6..fdfb3bf218b8 100644 --- a/zephyr/lib/vregion.c +++ b/zephyr/lib/vregion.c @@ -396,6 +396,12 @@ void *vregion_alloc_align(struct vregion *vr, enum vregion_mem_type type, case VREGION_MEM_TYPE_LIFETIME: p = lifetime_alloc(vr, &vr->lifetime, size, alignment); break; + case VREGION_MEM_TYPE_INDIFFERENT: + if (vr->interim_initialized) + p = interim_alloc(vr, &vr->interim, size, alignment); + else + p = lifetime_alloc(vr, &vr->lifetime, size, alignment); + break; default: LOG_ERR("error: invalid memory type %d", type); p = NULL; From 4779c33f2639cadcfa470f036ed9ffe0fd4c2e73 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Tue, 12 May 2026 20:41:10 +0300 Subject: [PATCH 10/15] lib: objpool: use VREGION_MEM_TYPE_INDIFFERENT for pool allocations Object pool metadata does not need to reside in a specific partition. Use INDIFFERENT so pool blocks go to lifetime before the interim heap is created and to interim afterwards, without triggering premature interim heap initialization. Signed-off-by: Jyri Sarha --- src/lib/objpool.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/objpool.c b/src/lib/objpool.c index 6925e6f7070a..989cace258c0 100644 --- a/src/lib/objpool.c +++ b/src/lib/objpool.c @@ -44,10 +44,10 @@ static int objpool_add(struct objpool_head *head, unsigned int n, size_t size, u pobjpool = sof_heap_alloc(head->heap, flags, aligned_size + sizeof(*pobjpool), 0); else if (flags & SOF_MEM_FLAG_COHERENT) - pobjpool = vregion_alloc_coherent(head->vreg, VREGION_MEM_TYPE_INTERIM, + pobjpool = vregion_alloc_coherent(head->vreg, VREGION_MEM_TYPE_INDIFFERENT, aligned_size + sizeof(*pobjpool)); else - pobjpool = vregion_alloc(head->vreg, VREGION_MEM_TYPE_INTERIM, + pobjpool = vregion_alloc(head->vreg, VREGION_MEM_TYPE_INDIFFERENT, aligned_size + sizeof(*pobjpool)); if (!pobjpool) From 4c33deba6cbd558a6172d4da18c27fdd17fba557 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Wed, 13 May 2026 20:41:59 +0300 Subject: [PATCH 11/15] audio: module_adapter: add vregion_type to mod_alloc_ctx Add a vregion_type field to struct mod_alloc_ctx to track the current vregion allocation mode for each module. Initialize it to VREGION_MEM_TYPE_LIFETIME during module_adapter_mem_alloc() so that all allocations during module creation and init use the lifetime partition. Switch it to VREGION_MEM_TYPE_INTERIM at the start of module_adapter_prepare() so that runtime allocations use the interim partition. Signed-off-by: Jyri Sarha --- src/audio/module_adapter/module_adapter.c | 5 +++++ src/include/sof/audio/component.h | 1 + 2 files changed, 6 insertions(+) diff --git a/src/audio/module_adapter/module_adapter.c b/src/audio/module_adapter/module_adapter.c index 4729d7a9ae15..f21c8ce1849a 100644 --- a/src/audio/module_adapter/module_adapter.c +++ b/src/audio/module_adapter/module_adapter.c @@ -149,6 +149,8 @@ struct processing_module *module_adapter_mem_alloc(const struct comp_driver *drv memset(mod, 0, sizeof(*mod)); alloc->heap = mod_heap; alloc->vreg = mod_vreg; + /* Set vreg type to lifetime mode first, will become interim in module_adapter_prepare() */ + alloc->vregion_type = VREGION_MEM_TYPE_LIFETIME; mod->priv.resources.alloc = alloc; mod_resource_init(mod); @@ -422,6 +424,9 @@ int module_adapter_prepare(struct comp_dev *dev) int i = 0; comp_dbg(dev, "start"); + + /* Prepare has been called, from now on all vreg allocs should be INTERIM. */ + mod->priv.resources.alloc->vregion_type = VREGION_MEM_TYPE_INTERIM; #if CONFIG_IPC_MAJOR_4 /* allocate stream_params and retrieve the params from the basecfg if needed */ if (!mod->stream_params) { diff --git a/src/include/sof/audio/component.h b/src/include/sof/audio/component.h index 7fc9feb53736..814d4f6fab83 100644 --- a/src/include/sof/audio/component.h +++ b/src/include/sof/audio/component.h @@ -584,6 +584,7 @@ struct vregion; struct mod_alloc_ctx { struct k_heap *heap; struct vregion *vreg; + int vregion_type; /* enum vregion_mem_type */ }; /** From a990bd4b1dc129442724fa932cc7c13101c56754 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Tue, 12 May 2026 22:45:11 +0300 Subject: [PATCH 12/15] audio: comp_buffer: use vregion_type from mod_alloc_ctx Use vregion_type from strcut mod_alloc_ctx, when vregion is available, for buffer allocations. Signed-off-by: Jyri Sarha --- src/audio/buffers/comp_buffer.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/audio/buffers/comp_buffer.c b/src/audio/buffers/comp_buffer.c index cc38a7dfead7..c2b64ce04f3b 100644 --- a/src/audio/buffers/comp_buffer.c +++ b/src/audio/buffers/comp_buffer.c @@ -217,9 +217,9 @@ static struct comp_buffer *buffer_alloc_struct(struct mod_alloc_ctx *alloc, if (!alloc || !alloc->vreg) buffer = sof_heap_alloc(alloc ? alloc->heap : NULL, flags, sizeof(*buffer), 0); else if (is_shared) - buffer = vregion_alloc_coherent(alloc->vreg, VREGION_MEM_TYPE_INTERIM, sizeof(*buffer)); + buffer = vregion_alloc_coherent(alloc->vreg, alloc->vregion_type, sizeof(*buffer)); else - buffer = vregion_alloc(alloc->vreg, VREGION_MEM_TYPE_INTERIM, sizeof(*buffer)); + buffer = vregion_alloc(alloc->vreg, alloc->vregion_type, sizeof(*buffer)); if (!buffer) { tr_err(&buffer_tr, "could not alloc structure"); return NULL; From 1564f2d786afc0de118e7cb703633dc337199268 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Tue, 12 May 2026 12:08:20 +0300 Subject: [PATCH 13/15] vregions: mod_alloc: get vregions type from mod_alloc_ctx Get mod_alloc() vregions vregion_mem_type from struct mod_alloc_ctx. Signed-off-by: Jyri Sarha --- src/audio/module_adapter/module/generic.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/audio/module_adapter/module/generic.c b/src/audio/module_adapter/module/generic.c index 2989bceb160b..1643f956cf01 100644 --- a/src/audio/module_adapter/module/generic.c +++ b/src/audio/module_adapter/module/generic.c @@ -251,16 +251,18 @@ void *z_impl_mod_alloc_ext(struct processing_module *mod, uint32_t flags, size_t return NULL; } - /* Allocate memory for module */ + /* Allocate memory for module. Before init completes, use + * lifetime partition so allocations persist across prepare/reset. + */ void *ptr; if (!res->alloc->vreg) ptr = sof_heap_alloc(res->alloc->heap, flags, size, alignment); else if (flags & SOF_MEM_FLAG_COHERENT) - ptr = vregion_alloc_coherent_align(res->alloc->vreg, VREGION_MEM_TYPE_INTERIM, + ptr = vregion_alloc_coherent_align(res->alloc->vreg, res->alloc->vregion_type, size, alignment); else - ptr = vregion_alloc_align(res->alloc->vreg, VREGION_MEM_TYPE_INTERIM, + ptr = vregion_alloc_align(res->alloc->vreg, res->alloc->vregion_type, size, alignment); if (!ptr) { From 3012dac2d698d662898efe2f74cbc42ac239f8e8 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Wed, 13 May 2026 17:18:04 +0300 Subject: [PATCH 14/15] lib: fast-get: use vregion_type from mod_alloc_ctx Use vregion_type from struct mod_alloc_ctx to control which vregion memory type fast_get() uses for allocations. Signed-off-by: Jyri Sarha --- zephyr/lib/fast-get.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zephyr/lib/fast-get.c b/zephyr/lib/fast-get.c index 9c502284f082..01441808329a 100644 --- a/zephyr/lib/fast-get.c +++ b/zephyr/lib/fast-get.c @@ -194,8 +194,8 @@ const void *fast_get(struct mod_alloc_ctx *alloc, const void *dram_ptr, size_t s } if (alloc && alloc->vreg && size <= FAST_GET_MAX_COPY_SIZE) - /* A userspace allocation, that won't be shared */ - ret = vregion_alloc_align(alloc->vreg, VREGION_MEM_TYPE_INTERIM, alloc_size, + /* Use mem type set by caller (mod_fast_get sets per module state) */ + ret = vregion_alloc_align(alloc->vreg, alloc->vregion_type, alloc_size, alloc_align); else ret = sof_heap_alloc(heap, alloc_flags, alloc_size, alloc_align); From 29c38ded8ef9f1ed71e299e960e40d854c1e9ad9 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Mon, 11 May 2026 22:17:53 +0300 Subject: [PATCH 15/15] 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,