From 948c6f440e86d5e565cfacc638a1fc97237bab4f Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Mon, 23 Mar 2026 20:38:09 +0200 Subject: [PATCH 01/10] (---section submitted PRs START) From 009653e58a832752d5bee435348a964a91a9769d Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Mon, 17 Aug 2026 15:07:25 +0300 Subject: [PATCH 02/10] schedule: zephyr_ll: protect against a race in task free Add a check to ensure task state is what is expected after k_sem_take() returns in zephyr_ll_task_free(). This is needed to avoid a rare error hit when running stress tests with chain DMA in user-space LL builds. Issue is hard to reproduce, but similar error signature can be created by passing K_NO_WAIT to k_sem_take() and running a test with chain-dma pipeline. Add defensive code that handles this scenario and prints out a warning when unexpected return occurs. Tested with a custom build with K_NO_WAIT passed to k_sem_take(). Signed-off-by: Kai Vehmanen --- src/schedule/zephyr_ll.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/schedule/zephyr_ll.c b/src/schedule/zephyr_ll.c index 8e55695538ec..ca315012d56e 100644 --- a/src/schedule/zephyr_ll.c +++ b/src/schedule/zephyr_ll.c @@ -570,6 +570,7 @@ static int zephyr_ll_task_free(void *data, struct task *task) uint32_t flags; struct zephyr_ll_pdata *pdata = task->priv_data; bool must_wait, on_list = true; + int wait_ret = 0; zephyr_ll_assert_core(sch); @@ -617,10 +618,17 @@ static int zephyr_ll_task_free(void *data, struct task *task) if (must_wait) /* Wait for up to 100 periods */ - k_sem_take(pdata->sem_p, K_USEC(LL_TIMER_PERIOD_US * 100)); + wait_ret = k_sem_take(pdata->sem_p, K_USEC(LL_TIMER_PERIOD_US * 100)); /* Protect against racing with schedule_task() */ zephyr_ll_lock(sch, &flags); + + if (wait_ret && task->state != SOF_TASK_STATE_FREE) { + tr_warn(&ll_tr, "task %p still active on free (state %d, semret %d)", + task, task->state, wait_ret); + zephyr_ll_task_done(sch, task); + } + #if CONFIG_DYNAMIC_OBJECTS zephyr_ll_task_sem_free(task); #endif From bdea0bd32eef33c3540bedcd7962b0025dc86fc0 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Wed, 2 Sep 2026 14:11:41 +0300 Subject: [PATCH 03/10] ipc: userspace: don't fault when removing an already-sent IPC message z_vrfy_ipc_msg_list_remove() rejected any message that was not currently on ipc->msg_list by failing K_SYSCALL_VERIFY(found), which turns into a kernel oops. But ipc_msg_list_remove() is called from ipc_msg_free() / mod_ipc_msg_free() to drop a message that may or may not still be queued. The common case at stream stop / pipeline delete is freeing a message that has already been sent and dequeued: its list node is self-linked (empty), so it is not "found" and the verifier oopses the LL user thread with: os.z_vrfy_ipc_msg_list_remove: syscall z_vrfy_ipc_msg_list_remove ... failed check: found os.z_fatal_error: >>> ZEPHYR FATAL ERROR 3: Kernel oops on CPU 0 Relax the checks to avoid this scenario. If the msg->list is empty, it is safe to call z_impl_ipc_msg_list_remove(). The msg->list pointer itself is already verified with K_SYSCALL_MEMORY_WRITE(). Signed-off-by: Kai Vehmanen --- src/ipc/ipc-common.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/ipc/ipc-common.c b/src/ipc/ipc-common.c index d05895a3f13a..a736bd0f84a8 100644 --- a/src/ipc/ipc-common.c +++ b/src/ipc/ipc-common.c @@ -362,7 +362,17 @@ void z_vrfy_ipc_msg_list_remove(struct ipc_msg *msg) break; } } - K_OOPS(K_SYSCALL_VERIFY(found)); + + /* + * ipc_msg_list_remove() is normally called from ipc_msg_free() to drop + * a message that may or may not still be queued. A message that has + * already been sent (or was never queued) has a self-linked, empty list + * node, so removing it via list_item_del() is a harmless no-op that only + * touches &msg->list, which was already validated above. Only reject a + * non-empty node that is not on ipc->msg_list, i.e. one whose list + * pointers would make list_item_del() corrupt unrelated memory. + */ + K_OOPS(K_SYSCALL_VERIFY(found || list_is_empty(&msg->list))); z_impl_ipc_msg_list_remove(msg); } #include From d5a3d370a4e535a462fae7464b73fed0ac501e0a Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 3 Sep 2026 15:24:57 +0300 Subject: [PATCH 04/10] zephyr: vregion: stop logging spurious "read access denied" on free paths vregion_verify() asserts that the vregion metadata object is NOT accessible to the userspace context. It did this with K_OOPS(!K_SYSCALL_MEMORY_READ(vr, sizeof(*vr))); but K_SYSCALL_MEMORY_READ() emits an "os.vregion_verify: ... Memory region (size 88) read access denied" error via LOG_ERR precisely when the region is inaccessible - i.e. in the expected, correct case for a kernel-only vregion. Probe the mapping directly with arch_buffer_validate(), which performs the same check without logging, and oops only if the userspace context can actually read the metadata. No functional change to the security check; only the false-positive error logging is removed. Signed-off-by: Kai Vehmanen --- zephyr/lib/vregion.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/zephyr/lib/vregion.c b/zephyr/lib/vregion.c index e8cfb2347246..fb65906446fa 100644 --- a/zephyr/lib/vregion.c +++ b/zephyr/lib/vregion.c @@ -554,8 +554,15 @@ bool vregion_verify(struct vregion *vr) if (!vr) return false; - /* vregion instances must not be accessible to the userspace. */ - K_OOPS(!K_SYSCALL_MEMORY_READ(vr, sizeof(*vr))); + /* + * vregion instances must not be accessible to the userspace. + * + * Don't use K_SYSCALL_MEMORY_READ() here: it logs an "access denied" + * error whenever the region is inaccessible, which is exactly the + * expected (good) case for a kernel-only vregion. Omit false + * error messages by using arch_buffer_validate() directly. + */ + K_OOPS(arch_buffer_validate((void *)vr, sizeof(*vr), 0) == 0); size_t vr_size = 0; uintptr_t vr_start; From f8b8b29c78c52eb1be505d8c77b7fd086ef595d9 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Thu, 3 Sep 2026 20:28:24 +0300 Subject: [PATCH 05/10] 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 (cherry picked from commit deac1c8a5b26f7466e7de0266061c7b2915f6d6f) --- 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 ffb7e424a89e1a41319404321dc17739519a1d18 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Thu, 3 Sep 2026 20:36:33 +0300 Subject: [PATCH 06/10] 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 (cherry picked from commit 6407e81546647d9dddf6310425669e6d07212e3c) --- 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 98234daa082c..37af16aa9e1a 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 */ From 54291266fbfee3dbd25d69de5b388940dc896c5d Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Mon, 23 Mar 2026 20:38:09 +0200 Subject: [PATCH 07/10] (---section submitted PRs STOP) From 6e946232b9820c2c214442c4cc145fdce71513fe Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 13 Aug 2026 19:42:30 +0300 Subject: [PATCH 08/10] boards: intel: default to user-space LL for ptl and wcl Make the options from app/overlays/ptl/ll_userspace_overlay.conf the default for the Intel Panther Lake (ptl) and Wildcat Lake (wcl) build targets, so user-space Low-Latency audio pipelines are enabled without having to pass the overlay explicitly. As noted in the overlay header, once user-space LL is enabled for a target by default the settings belong in the SOF board file directly. For ptl the board already provides the user-space base (USERSPACE, dynamic threads, MMU L2 tables, domain partitions), so only the LL overlay options are added and the conflicting telemetry / cold-store / llext / modules defaults are flipped to match the overlay. wcl had no user-space base at all; since CONFIG_SOF_USERSPACE_LL depends on CONFIG_USERSPACE it would otherwise be silently dropped. Mirror ptl's user-space base into the wcl board file as well so LL actually takes effect there. The ll_userspace_overlay.conf file is kept unchanged; it now re-applies identical values and remains usable by development build scripts. Signed-off-by: Kai Vehmanen --- app/boards/intel_adsp_ace30_ptl.conf | 24 ++++++++++++++++-- app/boards/intel_adsp_ace30_wcl.conf | 37 ++++++++++++++++++++++++++-- 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/app/boards/intel_adsp_ace30_ptl.conf b/app/boards/intel_adsp_ace30_ptl.conf index 6697f8d5523a..18a6836a9db5 100644 --- a/app/boards/intel_adsp_ace30_ptl.conf +++ b/app/boards/intel_adsp_ace30_ptl.conf @@ -21,8 +21,8 @@ CONFIG_KCPS_DYNAMIC_CLOCK_CONTROL=n CONFIG_PROBE=y CONFIG_PROBE_DMA_MAX=2 CONFIG_SOF_TELEMETRY=y -CONFIG_SOF_TELEMETRY_IO_PERFORMANCE_MEASUREMENTS=y -CONFIG_SOF_TELEMETRY_PERFORMANCE_MEASUREMENTS=y +CONFIG_SOF_TELEMETRY_IO_PERFORMANCE_MEASUREMENTS=n +CONFIG_SOF_TELEMETRY_PERFORMANCE_MEASUREMENTS=n CONFIG_COLD_STORE_EXECUTE_DRAM=y # SOF / loadable modules @@ -83,3 +83,23 @@ CONFIG_SOF_USERSPACE_PROXY=y CONFIG_MAX_THREAD_BYTES=3 CONFIG_MAX_DOMAIN_PARTITIONS=32 + +# Userspace LL (was app/overlays/ptl/ll_userspace_overlay.conf) +# Run Low-Latency audio pipelines in user-space threads by default. +CONFIG_SOF_USERSPACE_LL=y +CONFIG_SOF_USERSPACE_INTERFACE_DMA=y +CONFIG_DAI_USERSPACE=y + +# Settings currently required to enable user-space LL. The cold-store, +# telemetry, loadable-module and misc feature disables above/here are not +# yet user-space compatible (see the former overlay for rationale). +CONFIG_COLD_STORE_EXECUTE_DEBUG=n +CONFIG_SOF_BOOT_TEST_ALLOWED=n +CONFIG_CROSS_CORE_STREAM=n +CONFIG_INTEL_ADSP_MIC_PRIVACY=n +CONFIG_XRUN_NOTIFICATIONS_ENABLE=n +# Extend the shared LL user-space heap to 0.5MB. This +# is current maximum for PTL builds with virtual heap +# enabled and uses the vmh allocation intended for KBP. +# TODO: needs some better solution to allocate +CONFIG_SOF_ZEPHYR_SYS_USER_HEAP_SIZE=0x80000 diff --git a/app/boards/intel_adsp_ace30_wcl.conf b/app/boards/intel_adsp_ace30_wcl.conf index 98f5ba341fc3..091f8e6e1879 100644 --- a/app/boards/intel_adsp_ace30_wcl.conf +++ b/app/boards/intel_adsp_ace30_wcl.conf @@ -21,8 +21,8 @@ CONFIG_KCPS_DYNAMIC_CLOCK_CONTROL=n CONFIG_PROBE=y CONFIG_PROBE_DMA_MAX=2 CONFIG_SOF_TELEMETRY=y -CONFIG_SOF_TELEMETRY_IO_PERFORMANCE_MEASUREMENTS=y -CONFIG_SOF_TELEMETRY_PERFORMANCE_MEASUREMENTS=y +CONFIG_SOF_TELEMETRY_IO_PERFORMANCE_MEASUREMENTS=n +CONFIG_SOF_TELEMETRY_PERFORMANCE_MEASUREMENTS=n CONFIG_COLD_STORE_EXECUTE_DRAM=y # SOF / loadable modules @@ -69,3 +69,36 @@ CONFIG_PM_DEVICE_RUNTIME_ASYNC=n CONFIG_LOG_BACKEND_ADSP=n CONFIG_LOG_FLUSH_SLEEP_US=5000 CONFIG_WINSTREAM_CONSOLE=n + +# Userspace base (mirrored from intel_adsp_ace30_ptl.conf) +# Required so that user-space LL (below) can actually be enabled, since +# CONFIG_SOF_USERSPACE_LL depends on CONFIG_USERSPACE. +CONFIG_USERSPACE=y +CONFIG_DYNAMIC_THREAD=y +CONFIG_DYNAMIC_THREAD_ALLOC=y +CONFIG_DYNAMIC_THREAD_PREFER_ALLOC=y +CONFIG_SOF_STACK_SIZE=8192 +CONFIG_SOF_USERSPACE_PROXY=y +CONFIG_MAX_THREAD_BYTES=3 +CONFIG_MAX_DOMAIN_PARTITIONS=32 +CONFIG_XTENSA_MMU_NUM_L2_TABLES=128 + +# Userspace LL (was app/overlays/ptl/ll_userspace_overlay.conf) +# Run Low-Latency audio pipelines in user-space threads by default. +CONFIG_SOF_USERSPACE_LL=y +CONFIG_SOF_USERSPACE_INTERFACE_DMA=y +CONFIG_DAI_USERSPACE=y + +# Settings currently required to enable user-space LL. The cold-store, +# telemetry, loadable-module and misc feature disables above/here are not +# yet user-space compatible (see the former overlay for rationale). +CONFIG_COLD_STORE_EXECUTE_DEBUG=n +CONFIG_SOF_BOOT_TEST_ALLOWED=n +CONFIG_CROSS_CORE_STREAM=n +CONFIG_INTEL_ADSP_MIC_PRIVACY=n +CONFIG_XRUN_NOTIFICATIONS_ENABLE=n +# Extend the shared LL user-space heap to 0.5MB. This +# is current maximum for WCL builds with virtual heap +# enabled and uses the vmh allocation intended for KBP. +# TODO: needs some better solution to allocate +CONFIG_SOF_ZEPHYR_SYS_USER_HEAP_SIZE=0x80000 From dc0d4a3cb2fc09a6bf0a17f563456f5a1f7e5855 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 3 Sep 2026 17:32:32 +0300 Subject: [PATCH 09/10] app: boards: intel_adsp_ace30: bump max domains to 64 The upstream SOF nocodec topologies require more than 32 memory partitions. Bump the max to 64. Signed-off-by: Kai Vehmanen --- app/boards/intel_adsp_ace30_ptl.conf | 2 +- app/boards/intel_adsp_ace30_wcl.conf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/boards/intel_adsp_ace30_ptl.conf b/app/boards/intel_adsp_ace30_ptl.conf index 18a6836a9db5..5e47c0303689 100644 --- a/app/boards/intel_adsp_ace30_ptl.conf +++ b/app/boards/intel_adsp_ace30_ptl.conf @@ -82,7 +82,7 @@ CONFIG_SOF_STACK_SIZE=8192 CONFIG_SOF_USERSPACE_PROXY=y CONFIG_MAX_THREAD_BYTES=3 -CONFIG_MAX_DOMAIN_PARTITIONS=32 +CONFIG_MAX_DOMAIN_PARTITIONS=64 # Userspace LL (was app/overlays/ptl/ll_userspace_overlay.conf) # Run Low-Latency audio pipelines in user-space threads by default. diff --git a/app/boards/intel_adsp_ace30_wcl.conf b/app/boards/intel_adsp_ace30_wcl.conf index 091f8e6e1879..0f051172a552 100644 --- a/app/boards/intel_adsp_ace30_wcl.conf +++ b/app/boards/intel_adsp_ace30_wcl.conf @@ -80,7 +80,7 @@ CONFIG_DYNAMIC_THREAD_PREFER_ALLOC=y CONFIG_SOF_STACK_SIZE=8192 CONFIG_SOF_USERSPACE_PROXY=y CONFIG_MAX_THREAD_BYTES=3 -CONFIG_MAX_DOMAIN_PARTITIONS=32 +CONFIG_MAX_DOMAIN_PARTITIONS=64 CONFIG_XTENSA_MMU_NUM_L2_TABLES=128 # Userspace LL (was app/overlays/ptl/ll_userspace_overlay.conf) From 2d40017d4bb569dc0cc66861dc4c74069a5a3e71 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Fri, 4 Sep 2026 16:47:20 +0300 Subject: [PATCH 10/10] app: overlays: ptl: ll_userspace_overlay: increase memory partitions Increase the max limit for memory partitions to allow running SOF with sof-ptl-nocodec.tplg when running all pipelines in user-space. Signed-off-by: Kai Vehmanen --- app/overlays/ptl/ll_userspace_overlay.conf | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/overlays/ptl/ll_userspace_overlay.conf b/app/overlays/ptl/ll_userspace_overlay.conf index b799736240f8..dfd5ad611851 100644 --- a/app/overlays/ptl/ll_userspace_overlay.conf +++ b/app/overlays/ptl/ll_userspace_overlay.conf @@ -37,3 +37,6 @@ CONFIG_XRUN_NOTIFICATIONS_ENABLE=n # enabled and uses the vmh allocation intended for KBP. # TODO: needs some better solution to allocate CONFIG_SOF_ZEPHYR_SYS_USER_HEAP_SIZE=0x80000 + +# sof-ptl-nocodec.tplg requires more than 32 partitions for DP +CONFIG_MAX_DOMAIN_PARTITIONS=64