From deac1c8a5b26f7466e7de0266061c7b2915f6d6f Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Thu, 3 Sep 2026 20:28:24 +0300 Subject: [PATCH 1/2] pipeline: replace heap member with alloc context Replace the bare k_heap pointer in struct pipeline with a mod_alloc_ctx object. The context is created in pipeline_new() and freed symmetrically in pipeline_free(). No vregion is associated with this context yet, so allocation behavior is unchanged: sof_ctx_alloc()/sof_ctx_zalloc()/sof_ctx_free() fall back to the plain heap when the context has no vregion. Update all remaining pipeline->heap consumers, including the pipeline and trigger task allocations in pipeline-schedule.c, to go through the alloc context instead of the heap pointer directly. Signed-off-by: Jyri Sarha --- src/audio/pipeline/pipeline-graph.c | 27 +++++++++++++++++++------- src/audio/pipeline/pipeline-schedule.c | 14 ++++++------- src/include/sof/audio/pipeline.h | 3 ++- zephyr/test/userspace/test_ll_task.c | 2 +- 4 files changed, 30 insertions(+), 16 deletions(-) diff --git a/src/audio/pipeline/pipeline-graph.c b/src/audio/pipeline/pipeline-graph.c index e6f56eaf7096..e8fbb6333c14 100644 --- a/src/audio/pipeline/pipeline-graph.c +++ b/src/audio/pipeline/pipeline-graph.c @@ -174,6 +174,7 @@ void pipeline_posn_grant_access(struct k_thread *thread) 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; @@ -184,17 +185,24 @@ struct pipeline *pipeline_new(struct k_heap *heap, uint32_t pipeline_id, uint32_ /* show heap status */ heap_trace_all(0); + alloc = sof_heap_alloc(heap, SOF_MEM_FLAG_USER, sizeof(*alloc), 0); + if (!alloc) { + pipe_cl_err("Failed to allocate pipeline alloc context"); + return NULL; + } + + memset(alloc, 0, sizeof(*alloc)); + alloc->heap = heap; + /* allocate new pipeline */ - p = sof_heap_alloc(heap, SOF_MEM_FLAG_USER, sizeof(*p), 0); + p = sof_ctx_zalloc(alloc, SOF_MEM_FLAG_USER, sizeof(*p), 0); if (!p) { pipe_cl_err("Out of Memory"); - return NULL; + goto free_alloc; } - memset(p, 0, sizeof(*p)); - /* init pipeline */ - p->heap = heap; + p->alloc = alloc; p->comp_id = comp_id; p->priority = priority; p->pipeline_id = pipeline_id; @@ -236,7 +244,9 @@ 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: + sof_heap_free(heap, alloc); return NULL; } @@ -321,6 +331,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"); /* @@ -336,7 +348,8 @@ 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); + sof_heap_free(alloc->heap, alloc); /* show heap status */ heap_trace_all(0); diff --git a/src/audio/pipeline/pipeline-schedule.c b/src/audio/pipeline/pipeline-schedule.c index 0f00dcf1701c..fc8daad6e378 100644 --- a/src/audio/pipeline/pipeline-schedule.c +++ b/src/audio/pipeline/pipeline-schedule.c @@ -349,7 +349,7 @@ static struct task *ipc4_pipeline_trigger_task_init(struct pipeline *p, uint32_t { struct task *task; - task = sof_heap_alloc(p->heap, SOF_MEM_FLAG_USER, sizeof(*task), 0); + task = sof_ctx_alloc(p->alloc, SOF_MEM_FLAG_USER, sizeof(*task), 0); if (!task) return NULL; @@ -358,7 +358,7 @@ static struct task *ipc4_pipeline_trigger_task_init(struct pipeline *p, uint32_t /* All trigger tasks use the highest priority, regardless of pipeline priority. */ if (schedule_task_init_ll(task, SOF_UUID(pipe_trigger_task_uuid), type, -1, ipc4_pipeline_trigger_task, p, p->core, 0) < 0) { - sof_heap_free(p->heap, task); + sof_ctx_free(p->alloc, task); return NULL; } @@ -370,8 +370,8 @@ static struct task *pipeline_task_init(struct pipeline *p, uint32_t type) { struct pipeline_task *task = NULL; - task = sof_heap_alloc(p->heap, SOF_MEM_FLAG_USER, - sizeof(*task), 0); + task = sof_ctx_alloc(p->alloc, SOF_MEM_FLAG_USER, + sizeof(*task), 0); if (!task) return NULL; @@ -385,7 +385,7 @@ static struct task *pipeline_task_init(struct pipeline *p, uint32_t type) ipc3_pipeline_task, #endif p, p->core, 0) < 0) { - sof_heap_free(p->heap, task); + sof_ctx_free(p->alloc, task); return NULL; } @@ -562,14 +562,14 @@ void pipeline_comp_ll_task_free(struct pipeline *p) delayed_trigger_owner[p->core] = NULL; if (p->trigger_task) - sof_heap_free(p->heap, p->trigger_task); + sof_ctx_free(p->alloc, p->trigger_task); #endif if (p->pipe_task) { #if !CONFIG_LIBRARY || UNIT_TEST schedule_task_free(p->pipe_task); #endif - sof_heap_free(p->heap, p->pipe_task); + sof_ctx_free(p->alloc, p->pipe_task); } } diff --git a/src/include/sof/audio/pipeline.h b/src/include/sof/audio/pipeline.h index 858c81d98a2f..688fa6fc12dc 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 mod_alloc_ctx; /* * Pipeline status to stop execution of current path, but to keep the @@ -53,7 +54,7 @@ struct k_heap; * Audio pipeline. */ struct pipeline { - struct k_heap *heap; /**< heap used for allocating this pipeline */ + struct mod_alloc_ctx *alloc; /**< alloc context used for allocating this pipeline */ uint32_t comp_id; /**< component id for pipeline */ uint32_t pipeline_id; /**< pipeline id */ uint32_t sched_id; /**< Scheduling component id */ diff --git a/zephyr/test/userspace/test_ll_task.c b/zephyr/test/userspace/test_ll_task.c index 1e31e01538b7..f6a63b7194bd 100644 --- a/zephyr/test/userspace/test_ll_task.c +++ b/zephyr/test/userspace/test_ll_task.c @@ -113,7 +113,7 @@ static void pipeline_check(void) zassert_not_null(p, "pipeline creation failed"); /* Verify heap assignment */ - zassert_equal(p->heap, heap, "pipeline heap not equal to user heap"); + zassert_equal(p->alloc->heap, heap, "pipeline heap not equal to user heap"); /* Verify pipeline properties */ zassert_equal(p->pipeline_id, pipeline_id, "pipeline id mismatch"); From 6407e81546647d9dddf6310425669e6d07212e3c Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Thu, 3 Sep 2026 20:36:33 +0300 Subject: [PATCH 2/2] pipeline: allocate shared vregion for LL modules in the pipeline Create a per-pipeline vregion in pipeline_new() when the IPC4 pipeline extension payload specifies the required heap size, and attach it to the pipeline's alloc context. LL modules on a pipeline with a vregion use it as their allocation backend via vregion_get(), instead of the driver's default heap, and share the pipeline's mod_alloc_ctx. 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. Also the behaviour in the case the where ppl_alloc is not available remains unchanged. 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. Setting of dev->pipeline is moved earlier in module_adapter_new_ext() so that we can still use module_adapter_mem_free() in its error handling. Call vregion_set_interim() for the pipeline vregion in pipeline_complete() to switch the allocator to interim mode after all lifetime allocations are done, and release it in pipeline_free(), warning if the refcount does not reach zero. Signed-off-by: Jyri Sarha --- src/audio/module_adapter/module_adapter.c | 155 ++++++++++++---------- src/audio/pipeline/pipeline-graph.c | 24 +++- 2 files changed, 105 insertions(+), 74 deletions(-) diff --git a/src/audio/module_adapter/module_adapter.c b/src/audio/module_adapter/module_adapter.c index 9e6720424bcf..99da86946997 100644 --- a/src/audio/module_adapter/module_adapter.c +++ b/src/audio/module_adapter/module_adapter.c @@ -77,11 +77,12 @@ 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 mod_alloc_ctx *ppl_alloc) { - struct k_heap *mod_heap; - struct vregion *mod_vreg; + struct k_heap *mod_heap = NULL; struct processing_module *mod; + struct mod_alloc_ctx *alloc; struct comp_dev *dev; /* * For DP shared modules the struct processing_module object must be @@ -92,49 +93,53 @@ 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)) { - 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; - } + if (use_ppl_alloc) { + /* LL modules share the pipeline's alloc context */ + alloc = ppl_alloc; + vregion_get(alloc->vreg); + } else { + struct vregion *mod_vreg = NULL; + + if (IS_ENABLED(CONFIG_SOF_VREGIONS) && IS_ENABLED(CONFIG_USERSPACE) && + !IS_ENABLED(CONFIG_SOF_USERSPACE_USE_DRIVER_HEAP) && + config->proc_domain == COMP_PROCESSING_DOMAIN_DP) { + 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; + } #ifdef CONFIG_SOF_USERSPACE_LL - mod_heap = sof_sys_user_heap_get(); -#else - mod_heap = NULL; + mod_heap = sof_sys_user_heap_get(); #endif - } else { + } else { #ifdef CONFIG_SOF_USERSPACE_LL - mod_heap = sof_sys_user_heap_get(); - comp_cl_dbg(drv, "using ll user heap for module"); + mod_heap = sof_sys_user_heap_get(); + comp_cl_dbg(drv, "using ll user heap for module"); #else - mod_heap = drv->user_heap; + mod_heap = drv->user_heap; #endif - mod_vreg = NULL; - } + } + alloc = sof_heap_alloc(mod_heap, flags, sizeof(*alloc), 0); + if (!alloc) { + vregion_put(mod_vreg); + return NULL; + } - if (!mod_vreg) - mod = sof_heap_alloc(mod_heap, flags, sizeof(*mod), 0); - else if (flags & SOF_MEM_FLAG_COHERENT) - mod = vregion_alloc_coherent(mod_vreg, sizeof(*mod)); - else - mod = vregion_alloc(mod_vreg, sizeof(*mod)); + memset(alloc, 0, sizeof(*alloc)); + alloc->heap = mod_heap; + alloc->vreg = mod_vreg; + } + mod = sof_ctx_alloc(alloc, flags, sizeof(*mod), 0); 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); - - if (!alloc) - goto ealloc; - memset(mod, 0, sizeof(*mod)); - alloc->heap = mod_heap; - alloc->vreg = mod_vreg; mod->priv.resources.alloc = alloc; mod_resource_init(mod); @@ -144,11 +149,7 @@ 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) - dev = vregion_alloc_coherent(mod_vreg, sizeof(*dev)); - else - dev = sof_heap_alloc(mod_heap, SOF_MEM_FLAG_COHERENT, sizeof(*dev), 0); - + dev = sof_ctx_alloc(alloc, SOF_MEM_FLAG_COHERENT, sizeof(*dev), 0); if (!dev) { comp_cl_err(drv, "failed to allocate memory for comp_dev"); goto edev; @@ -163,14 +164,11 @@ struct processing_module *module_adapter_mem_alloc(const struct comp_driver *drv return mod; edev: - sof_heap_free(mod_heap, alloc); -ealloc: - if (mod_vreg) - vregion_free(mod_vreg, mod); - else - sof_heap_free(mod_heap, mod); + sof_ctx_free(alloc, mod); emod: - vregion_put(mod_vreg); + vregion_put(alloc->vreg); + if (!use_ppl_alloc) + sof_heap_free(mod_heap, alloc); return NULL; } @@ -178,26 +176,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; - - vregion_free(mod_vreg, mod->dev); - vregion_free(mod_vreg, mod); - if (!vregion_put(mod_vreg)) - 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); } } @@ -248,8 +247,19 @@ 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); + struct mod_alloc_ctx *ppl_alloc = NULL; +#if CONFIG_IPC_MAJOR_4 + struct ipc_comp_dev *ipc_pipe; + struct ipc *ipc = ipc_get(); + + /* 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_alloc = ipc_pipe->pipeline->alloc; +#endif + struct processing_module *mod = module_adapter_mem_alloc(drv, config, ext_init, ppl_alloc); if (!mod) return NULL; @@ -273,6 +283,21 @@ struct comp_dev *module_adapter_new_ext(const struct comp_driver *drv, dst->ext_data = &ext_data; #endif +#if CONFIG_IPC_MAJOR_4 + /* + * Set the pipeline pointer if ipc_pipe is valid. Do this + * early so that we can use module_adapter_mem_free() in error + * handling. + */ + if (ipc_pipe) { + dev->pipeline = ipc_pipe->pipeline; + + /* LL modules have the same period as the pipeline */ + if (dev->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_LL) + dev->period = ipc_pipe->pipeline->period; + } +#endif + #if CONFIG_ZEPHYR_DP_SCHEDULER /* create a task for DP processing */ if (config->proc_domain == COMP_PROCESSING_DOMAIN_DP) { @@ -306,22 +331,6 @@ struct comp_dev *module_adapter_new_ext(const struct comp_driver *drv, else 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; - - /* LL modules have the same period as the pipeline */ - if (dev->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_LL) - dev->period = ipc_pipe->pipeline->period; - } -#endif - /* Init processing module */ ret = module_init(mod); if (ret) { diff --git a/src/audio/pipeline/pipeline-graph.c b/src/audio/pipeline/pipeline-graph.c index e8fbb6333c14..90fc4c2804c3 100644 --- a/src/audio/pipeline/pipeline-graph.c +++ b/src/audio/pipeline/pipeline-graph.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -194,6 +195,18 @@ struct pipeline *pipeline_new(struct k_heap *heap, uint32_t pipeline_id, uint32_ memset(alloc, 0, sizeof(*alloc)); alloc->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) { + size_t buf_size = pparams->mem_data->heap_bytes; + uintptr_t vreg_start; + + alloc->vreg = vregion_create_map(&vreg_start, &buf_size); + if (!alloc->vreg) + pipe_cl_err("Failed to create pipeline vregion of %zu bytes, using heap", + pparams->mem_data->heap_bytes); + } + /* allocate new pipeline */ p = sof_ctx_zalloc(alloc, SOF_MEM_FLAG_USER, sizeof(*p), 0); if (!p) { @@ -246,7 +259,8 @@ struct pipeline *pipeline_new(struct k_heap *heap, uint32_t pipeline_id, uint32_ free: sof_ctx_free(alloc, p); free_alloc: - sof_heap_free(heap, alloc); + vregion_put(alloc->vreg); + rfree(alloc); return NULL; } @@ -349,6 +363,10 @@ int pipeline_free(struct pipeline *p) /* now free the pipeline */ sof_ctx_free(alloc, p); + + /* free alloc context and vregion */ + if (vregion_put(alloc->vreg)) + pipe_cl_warn("pipeline vregion still in use"); sof_heap_free(alloc->heap, alloc); /* show heap status */ @@ -426,6 +444,10 @@ int pipeline_complete(struct pipeline *p, struct comp_dev *source, p->source_comp = source; p->sink_comp = sink; + + if (p->alloc && p->alloc->vreg) + vregion_set_interim(p->alloc->vreg); + p->status = COMP_STATE_READY; /* show heap status */