From a6a314bc656c776639c34dceb06e928f6062b633 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 9 Sep 2026 09:48:38 +0300 Subject: [PATCH 1/7] topology2: decoder/encoder: bump heap/stack requirement for DP userspace The generic widget-common.conf defaults (24576 bytes heap, 8192 bytes stack) are sized for the general case, but Cadence codecs running as DP modules under CONFIG_SOF_USERSPACE_APPLICATION need more of both: - The codec's own memory tables (persist/scratch/input/output) can exceed the default heap budget on their own. On top of that, every buffer connecting to a DP module is also carved out of that same module's vregion (ipc4_comp_connect() allocates from dp->mod->priv.resources.alloc), so the requirement has to cover the codec's memory tables plus all of its connected buffers, not just the codec. - Codec init (e.g. xa_aac_dec, xa_mp3_enc) can need more stack than the default when run inside the DP userspace thread. Bump stack_bytes_requirement to 16384 for both decoder and encoder widgets. Bump heap_bytes_requirement to 196608 for the decoder: AAC and MP3 fit within 131072, but FLAC's own persist/scratch/input/output tables plus its DP-boundary ring buffers measure ~152KB, so 131072 isn't enough once FLAC or PCM are exercised. Encoder is left at 131072, unverified against a codec that needs more. Verified on hardware: AAC, MP3, FLAC and PCM decode all work. Signed-off-by: Peter Ujfalusi --- .../topology/topology2/include/components/decoder.conf | 10 ++++++++++ .../topology/topology2/include/components/encoder.conf | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/tools/topology/topology2/include/components/decoder.conf b/tools/topology/topology2/include/components/decoder.conf index b01a1c56d050..79788de728ae 100644 --- a/tools/topology/topology2/include/components/decoder.conf +++ b/tools/topology/topology2/include/components/decoder.conf @@ -57,6 +57,16 @@ Class.Widget."decoder" { # cadence codec UUID uuid "43:84:21:d8:f3:5f:4c:4a:b3:88:6c:fe:07:b9:56:aa" + + # Cadence codec memory tables (persist/scratch/input/output) can exceed the + # generic widget-common.conf default; give decoders more headroom. This same + # vregion also backs every buffer bound to a DP module (ipc4_comp_connect() + # allocates from dp->mod->priv.resources.alloc), so it must cover the + # codec's own memory tables plus every connected buffer, not just the codec. + heap_bytes_requirement 196608 + # Cadence codec init (e.g. xa_aac_dec) can need more stack than the + # generic widget-common.conf default when run in the DP userspace thread. + stack_bytes_requirement 16384 no_pm "true" num_output_pins 1 num_input_pins 1 diff --git a/tools/topology/topology2/include/components/encoder.conf b/tools/topology/topology2/include/components/encoder.conf index cde2346e3885..e8be2e6adb45 100644 --- a/tools/topology/topology2/include/components/encoder.conf +++ b/tools/topology/topology2/include/components/encoder.conf @@ -57,6 +57,16 @@ Class.Widget."encoder" { # cadence codec UUID uuid "43:84:21:d8:f3:5f:4c:4a:b3:88:6c:fe:07:b9:56:aa" + + # Cadence codec memory tables (persist/scratch/input/output) can exceed the + # generic widget-common.conf default; give encoders more headroom. This same + # vregion also backs every buffer bound to a DP module (ipc4_comp_connect() + # allocates from dp->mod->priv.resources.alloc), so it must cover the + # codec's own memory tables plus every connected buffer, not just the codec. + heap_bytes_requirement 131072 + # Cadence codec init (e.g. xa_mp3_enc) can need more stack than the + # generic widget-common.conf default when run in the DP userspace thread. + stack_bytes_requirement 16384 no_pm "true" num_output_pins 1 num_input_pins 1 From 8fe441759727242ba9138f68520c77fe1a6ea263 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 9 Sep 2026 09:43:16 +0300 Subject: [PATCH 2/7] library_manager: skip LLEXT domain add/remove for native modules llext_manager_add_domain()/llext_manager_rm_domain() crashed (llext_manager_mod_find() dereferencing an invalid ctx) when called for a native (non-LLEXT) module scheduled on the DP+userspace-application path. lib_id == 0 is reserved and never populated by a loaded library, but was not guarded against. Skip the LLEXT domain add/remove entirely when LIB_MANAGER_GET_LIB_ID(module_id) == 0. llext_manager_mod_find() also needs its own guard against ctx->n_mod == 0: that's reachable for a genuine (non-NULL) library context, e.g. before its module segments are registered, and without the check the i == 0 case in the loop below reads ctx->mod[-1], out of bounds. Signed-off-by: Peter Ujfalusi --- src/library_manager/llext_manager.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/library_manager/llext_manager.c b/src/library_manager/llext_manager.c index 2db4edaacc8e..72da2d2a86d5 100644 --- a/src/library_manager/llext_manager.c +++ b/src/library_manager/llext_manager.c @@ -590,6 +590,14 @@ static int llext_manager_mod_find(const struct lib_manager_mod_ctx *ctx, unsigne { unsigned int i; + /* + * n_mod == 0 is reachable for a genuine (non-NULL) library context, e.g. + * before its module segments are registered - without this check the + * i == 0 case below reads ctx->mod[-1], out of bounds. + */ + if (!ctx->n_mod) + return -ENOENT; + for (i = 0; i < ctx->n_mod; i++) if (ctx->mod[i].start_idx > idx) break; @@ -1002,6 +1010,11 @@ static int llext_manager_add_mod_domain(struct lib_manager_module *mctx, struct int llext_manager_add_domain(const uint32_t component_id, struct k_mem_domain *domain) { const uint32_t module_id = IPC4_MOD_ID(component_id); + + /* Native (base firmware) modules aren't managed by lib_manager, nothing to add */ + if (!LIB_MANAGER_GET_LIB_ID(module_id)) + return 0; + struct lib_manager_mod_ctx *ctx = lib_manager_get_mod_ctx(module_id); const uint32_t entry_index = LIB_MANAGER_GET_MODULE_INDEX(module_id); const int mod_idx = llext_manager_mod_find(ctx, entry_index); @@ -1087,6 +1100,11 @@ static int llext_manager_rm_mod_domain(struct lib_manager_module *mctx, struct k int llext_manager_rm_domain(const uint32_t component_id, struct k_mem_domain *domain) { const uint32_t module_id = IPC4_MOD_ID(component_id); + + /* Native (base firmware) modules aren't managed by lib_manager, nothing to remove */ + if (!LIB_MANAGER_GET_LIB_ID(module_id)) + return 0; + struct lib_manager_mod_ctx *ctx = lib_manager_get_mod_ctx(module_id); const uint32_t entry_index = LIB_MANAGER_GET_MODULE_INDEX(module_id); const int mod_idx = llext_manager_mod_find(ctx, entry_index); From 8021f1d83fd2b1b0f030131306c4773124e96ba3 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 9 Sep 2026 09:43:38 +0300 Subject: [PATCH 3/7] schedule: dp: flatten ext_data across the DP userspace thread boundary cfg->ext_data points to a stack variable in module_adapter_new_ext(), valid only synchronously - but SOF_IPC4_MOD_INIT_INSTANCE runs asynchronously in the DP userspace thread, so ops->init() ended up reading a stale pointer into a different thread's stack (privilege violation, not a simple NULL deref). Add ext_data to union scheduler_dp_thread_ipc_param, flatten it by value in ipc_thread_flatten() (copying it while still on the calling thread, before the pointer goes stale), and repoint pmod->priv.cfg.ext_data at the flattened copy in ipc_thread_unflatten_run() before calling ops->init(), clearing it again afterwards to match the existing "not valid outside init()" convention. Signed-off-by: Peter Ujfalusi --- src/audio/module_adapter/module/generic.c | 10 +++++++--- src/include/sof/schedule/dp_schedule.h | 6 ++++++ src/schedule/zephyr_dp_schedule_application.c | 20 +++++++++++++++++++ 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/audio/module_adapter/module/generic.c b/src/audio/module_adapter/module/generic.c index 3e328b093962..166bbb259b59 100644 --- a/src/audio/module_adapter/module/generic.c +++ b/src/audio/module_adapter/module/generic.c @@ -124,9 +124,13 @@ int module_init(struct processing_module *mod) /* Now we can proceed with module specific initialization */ #if CONFIG_SOF_USERSPACE_APPLICATION - if (mod->dev->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_DP) - ret = scheduler_dp_thread_ipc(mod, SOF_IPC4_MOD_INIT_INSTANCE, NULL); - else + if (mod->dev->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_DP) { + union scheduler_dp_thread_ipc_param param = { + .ext_data = mod->priv.cfg.ext_data, + }; + + ret = scheduler_dp_thread_ipc(mod, SOF_IPC4_MOD_INIT_INSTANCE, ¶m); + } else #endif ret = interface->init(mod); diff --git a/src/include/sof/schedule/dp_schedule.h b/src/include/sof/schedule/dp_schedule.h index 449bf2151872..2c72178cd766 100644 --- a/src/include/sof/schedule/dp_schedule.h +++ b/src/include/sof/schedule/dp_schedule.h @@ -17,6 +17,7 @@ #include struct processing_module; +struct module_ext_init_data; /** * @@ -115,6 +116,11 @@ struct sof_sink; */ union scheduler_dp_thread_ipc_param { const struct bind_info *bind_data; + /* SOF_IPC4_MOD_INIT_INSTANCE: ext_data points to the caller's stack frame and is + * only valid until scheduler_dp_thread_ipc() returns; ipc_thread_flatten() copies + * it by value into DP-thread-accessible memory before the DP thread runs init(). + */ + const struct module_ext_init_data *ext_data; struct { unsigned int trigger_cmd; enum ipc4_pipeline_state state; diff --git a/src/schedule/zephyr_dp_schedule_application.c b/src/schedule/zephyr_dp_schedule_application.c index 043d97a8c89b..0cf8c457b9b8 100644 --- a/src/schedule/zephyr_dp_schedule_application.c +++ b/src/schedule/zephyr_dp_schedule_application.c @@ -54,6 +54,14 @@ struct ipc4_flat { struct sof_source *source[CONFIG_MODULE_MAX_CONNECTIONS]; struct sof_sink *sink[CONFIG_MODULE_MAX_CONNECTIONS]; } pipeline_state; + /* + * SOF_IPC4_MOD_INIT_INSTANCE: a by-value copy of the caller's + * ext_data. The original lives on the calling thread's stack, + * which the DP thread's memory domain doesn't have access to. + * The pointers it contains reference the IPC mailbox, which stays + * valid and is covered by the DP thread's SOF_DP_PART_CFG partition. + */ + struct module_ext_init_data init_instance; }; }; @@ -73,6 +81,12 @@ static int ipc_thread_flatten(unsigned int cmd, const union scheduler_dp_thread_ flat->bind.bu = *param->bind_data->ipc4_data; flat->bind.type = param->bind_data->bind_type; break; + case SOF_IPC4_MOD_INIT_INSTANCE: + if (param->ext_data) + flat->init_instance = *param->ext_data; + else + flat->init_instance = (struct module_ext_init_data){ 0 }; + break; case SOF_IPC4_GLB_SET_PIPELINE_STATE: flat->pipeline_state.trigger_cmd = param->pipeline_state.trigger_cmd; switch (param->pipeline_state.trigger_cmd) { @@ -133,7 +147,13 @@ static void ipc_thread_unflatten_run(struct processing_module *pmod, struct ipc4 flat->ret = ops->free(pmod); break; case SOF_IPC4_MOD_INIT_INSTANCE: + /* + * Repoint ext_data at the copy ipc_thread_flatten() made in DP-thread- + * accessible memory; the original caller-stack copy is out of reach here. + */ + pmod->priv.cfg.ext_data = &flat->init_instance; flat->ret = ops->init(pmod); + pmod->priv.cfg.ext_data = NULL; break; case SOF_IPC4_GLB_SET_PIPELINE_STATE: switch (flat->pipeline_state.trigger_cmd) { From c2db6993e677f1493fa02408d62ecef97d8cce07 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 9 Sep 2026 09:44:00 +0300 Subject: [PATCH 4/7] module_adapter: cadence: make cadence_api_table const cadence_api_table[] is a compile-time-constant lookup table (codec id -> function pointer), populated once at link time and never written to at runtime, but declared without const - placing it in .data. Zephyr's Xtensa MMU marks .text/.rodata XTENSA_MMU_MAP_SHARED (globally accessible from every memory domain) but not .data, so the DP userspace thread's restricted domain couldn't reach it, causing a privilege-violation crash on the very first codec dispatch. Signed-off-by: Peter Ujfalusi --- src/audio/module_adapter/module/cadence.c | 2 +- src/include/sof/audio/module_adapter/module/cadence.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/audio/module_adapter/module/cadence.c b/src/audio/module_adapter/module/cadence.c index 672cef86c0a2..69af80a983db 100644 --- a/src/audio/module_adapter/module/cadence.c +++ b/src/audio/module_adapter/module/cadence.c @@ -13,7 +13,7 @@ LOG_MODULE_REGISTER(cadence_codec, CONFIG_SOF_LOG_LEVEL); /*****************************************************************************/ /* Cadence API functions array */ /*****************************************************************************/ -struct cadence_api cadence_api_table[] = { +const struct cadence_api cadence_api_table[] = { #ifdef CONFIG_CADENCE_CODEC_WRAPPER { .id = CADENCE_CODEC_WRAPPER_ID, diff --git a/src/include/sof/audio/module_adapter/module/cadence.h b/src/include/sof/audio/module_adapter/module/cadence.h index ccca668bf76a..05db21443ea4 100644 --- a/src/include/sof/audio/module_adapter/module/cadence.h +++ b/src/include/sof/audio/module_adapter/module/cadence.h @@ -97,7 +97,7 @@ struct ipc4_cadence_module_cfg { } __packed __aligned(4); #endif -extern struct cadence_api cadence_api_table[]; +extern const struct cadence_api cadence_api_table[]; int cadence_codec_set_configuration(struct processing_module *mod, uint32_t config_id, enum module_cfg_fragment_position pos, From feba77144728c4f4d51bbfb6cd578c1965a68b1a Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 9 Sep 2026 09:44:39 +0300 Subject: [PATCH 5/7] module_adapter: route mod_balloc_align() through the module's vregion z_impl_mod_balloc_align() called sof_heap_alloc(res->alloc->heap, ...) directly, bypassing the module's vregion entirely - unlike mod_alloc_ext(), which correctly checks vreg first via sof_ctx_alloc(). For DP/vregion-scoped modules res->alloc->heap is NULL, so this silently fell through to the global heap/rballoc(), handing back memory outside the DP thread's memory domain. This is what made cd->self (a codec's own library object, allocated via mod_balloc()) unreachable from inside the DP userspace thread. Route through sof_ctx_alloc(res->alloc, ...) like mod_alloc_ext() already does. Signed-off-by: Peter Ujfalusi --- src/audio/module_adapter/module/generic.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/audio/module_adapter/module/generic.c b/src/audio/module_adapter/module/generic.c index 166bbb259b59..639dbc954dbb 100644 --- a/src/audio/module_adapter/module/generic.c +++ b/src/audio/module_adapter/module/generic.c @@ -206,9 +206,9 @@ void *z_impl_mod_balloc_align(struct processing_module *mod, size_t size, size_t return NULL; } - /* Allocate buffer memory for module */ - void *ptr = sof_heap_alloc(res->alloc->heap, SOF_MEM_FLAG_USER | SOF_MEM_FLAG_LARGE_BUFFER, - size, alignment); + /* Allocate buffer memory for module, same as mod_alloc_ext() */ + void *ptr = sof_ctx_alloc(res->alloc, SOF_MEM_FLAG_USER | SOF_MEM_FLAG_LARGE_BUFFER, + size, alignment); if (!ptr) { comp_err(mod->dev, "Failed to alloc %zu bytes %zu alignment for comp %#x.", From f22e91b1360e6edb06c6fc4fc2d9f1904260eb4b Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Tue, 8 Sep 2026 15:45:50 +0200 Subject: [PATCH 6/7] pipeline: move End Of Stream to component DP components cannot access pipeline objects. Move the End Of Stream flag to the component type. Signed-off-by: Guennadi Liakhovetski --- src/audio/component.c | 2 +- src/audio/host-zephyr.c | 2 +- src/audio/module_adapter/module/cadence.c | 4 +-- src/audio/pipeline/pipeline-graph.c | 33 +++++++++++++++++++++++ src/include/sof/audio/component.h | 1 + src/include/sof/audio/pipeline.h | 8 +++++- src/ipc/ipc4/handler-user.c | 6 ++--- test/cmocka/src/audio/mux/demux_copy.c | 2 +- test/cmocka/src/audio/mux/mux_copy.c | 2 +- 9 files changed, 50 insertions(+), 10 deletions(-) diff --git a/src/audio/component.c b/src/audio/component.c index 90999deb841a..f798e0a971e0 100644 --- a/src/audio/component.c +++ b/src/audio/component.c @@ -506,7 +506,7 @@ static bool comp_check_eos(struct comp_dev *dev) enum sof_audio_buffer_state sink_state = AUDIOBUF_STATE_INITIAL; struct comp_buffer *buffer; - if (!dev->pipeline->expect_eos) + if (!dev->expect_eos) return false; comp_dev_for_each_producer(dev, buffer) { diff --git a/src/audio/host-zephyr.c b/src/audio/host-zephyr.c index 5038bcc78320..58a22253f20d 100644 --- a/src/audio/host-zephyr.c +++ b/src/audio/host-zephyr.c @@ -396,7 +396,7 @@ static inline bool host_handle_eos(struct host_data *hd, struct comp_dev *dev, struct sof_audio_buffer *buffer = &hd->local_buffer->audio_buffer; enum sof_audio_buffer_state state = audio_buffer_get_state(buffer); - if (!dev->pipeline->expect_eos) + if (!dev->expect_eos) return false; if (!avail_samples) { diff --git a/src/audio/module_adapter/module/cadence.c b/src/audio/module_adapter/module/cadence.c index 69af80a983db..7edfa032321f 100644 --- a/src/audio/module_adapter/module/cadence.c +++ b/src/audio/module_adapter/module/cadence.c @@ -541,7 +541,7 @@ int cadence_codec_process_data(struct processing_module *mod, return 0; } - if (dev->pipeline->expect_eos) { + if (dev->expect_eos) { /* Signal that the stream is expected to end anytime soon */ API_CALL(cd, XA_API_CMD_INPUT_OVER, 0, NULL, ret); if (ret != LIB_NO_ERROR) { @@ -596,7 +596,7 @@ int cadence_codec_process_data(struct processing_module *mod, return ret; } - if (dev->pipeline->expect_eos) { + if (dev->expect_eos) { /* * AAC decoder cannot signal DONE, check if it stopped * producing data when EOS is expected diff --git a/src/audio/pipeline/pipeline-graph.c b/src/audio/pipeline/pipeline-graph.c index e6f56eaf7096..7582eee1e53e 100644 --- a/src/audio/pipeline/pipeline-graph.c +++ b/src/audio/pipeline/pipeline-graph.c @@ -344,6 +344,39 @@ int pipeline_free(struct pipeline *p) return 0; } +static int pipeline_comp_set_eos(struct comp_dev *current, + struct comp_buffer *calling_buf, + struct pipeline_walk_context *ctx, int dir) +{ + if (ctx->comp_data != (void *)current->pipeline) + return 0; + + current->expect_eos = *(bool *)ctx->buff_data; + + return pipeline_for_each_comp(current, ctx, dir); +} + +void pipeline_set_eos(struct pipeline *p, bool eos) +{ + struct pipeline_walk_context walk_ctx = { + .comp_func = pipeline_comp_set_eos, + .comp_data = p, + .buff_data = &eos, + }; + struct comp_dev *start; + int dir; + + if (p->source_comp->direction == SOF_IPC_STREAM_PLAYBACK) { + dir = PPL_DIR_UPSTREAM; + start = p->sink_comp; + } else { + dir = PPL_DIR_DOWNSTREAM; + start = p->source_comp; + } + + walk_ctx.comp_func(start, NULL, &walk_ctx, dir); +} + static int pipeline_comp_complete(struct comp_dev *current, struct comp_buffer *calling_buf, struct pipeline_walk_context *ctx, int dir) diff --git a/src/include/sof/audio/component.h b/src/include/sof/audio/component.h index 672603e430c7..48d0764f26d8 100644 --- a/src/include/sof/audio/component.h +++ b/src/include/sof/audio/component.h @@ -647,6 +647,7 @@ struct comp_dev { /* runtime */ uint16_t state; /**< COMP_STATE_ */ uint32_t frames; /**< number of frames we copy to sink */ + bool expect_eos; /**< end of stream expected */ struct pipeline *pipeline; /**< pipeline we belong to */ struct task *task; /**< component's processing task used diff --git a/src/include/sof/audio/pipeline.h b/src/include/sof/audio/pipeline.h index 858c81d98a2f..1b704ed83fe1 100644 --- a/src/include/sof/audio/pipeline.h +++ b/src/include/sof/audio/pipeline.h @@ -70,7 +70,6 @@ struct pipeline { int32_t xrun_bytes; /* last xrun length */ uint32_t status; /* pipeline status */ struct tr_ctx tctx; /* trace settings */ - bool expect_eos; /* pipeline is expecting end of stream */ /* scheduling */ #ifdef CONFIG_IPC_MAJOR_4 @@ -225,6 +224,13 @@ void pipeline_posn_grant_access(struct k_thread *thread); */ int pipeline_reset(struct pipeline *p, struct comp_dev *host_cd); +/** + * \brief Sets End Of Stream state for all devices in the pipeline. + * \param[in] p pipeline. + * \param[in] eos End Of Stream state. + */ +void pipeline_set_eos(struct pipeline *p, bool eos); + /** * \brief Walks the pipeline graph for each component. * \param[in] current Current pipeline component. diff --git a/src/ipc/ipc4/handler-user.c b/src/ipc/ipc4/handler-user.c index c14bbe7a7ce5..5cea1bf7dbe4 100644 --- a/src/ipc/ipc4/handler-user.c +++ b/src/ipc/ipc4/handler-user.c @@ -256,7 +256,7 @@ int ipc4_pipeline_prepare(struct ipc_comp_dev *ppl_icd, uint32_t cmd) switch (cmd) { case SOF_IPC4_PIPELINE_STATE_RUNNING: - if (ppl_icd->pipeline->expect_eos) { + if (ppl_icd->pipeline->source_comp && ppl_icd->pipeline->source_comp->expect_eos) { ipc_cmd_err(&ipc_tr, "pipeline %d: Can't transition from EOS to RUNNING", ppl_icd->id); return IPC4_INVALID_REQUEST; @@ -320,7 +320,7 @@ int ipc4_pipeline_prepare(struct ipc_comp_dev *ppl_icd, uint32_t cmd) ppl_icd->id, status); return IPC4_INVALID_REQUEST; } - ppl_icd->pipeline->expect_eos = true; + pipeline_set_eos(ppl_icd->pipeline, true); return 0; /* Must return here. Any other transition clears expect_eos. */ /* special case - TODO */ case SOF_IPC4_PIPELINE_STATE_SAVED: @@ -334,7 +334,7 @@ int ipc4_pipeline_prepare(struct ipc_comp_dev *ppl_icd, uint32_t cmd) if (ret < 0) return IPC4_INVALID_REQUEST; - ppl_icd->pipeline->expect_eos = false; + pipeline_set_eos(ppl_icd->pipeline, false); return ret; } diff --git a/test/cmocka/src/audio/mux/demux_copy.c b/test/cmocka/src/audio/mux/demux_copy.c index 8e331446bbf4..cb789707306f 100644 --- a/test/cmocka/src/audio/mux/demux_copy.c +++ b/test/cmocka/src/audio/mux/demux_copy.c @@ -169,7 +169,7 @@ static int setup_test_case(void **state) dummy_pipe = test_malloc(sizeof(*dummy_pipe)); if (!dummy_pipe) return -ENOMEM; - dummy_pipe->expect_eos = false; + dev->expect_eos = false; dev->pipeline = dummy_pipe; mod = comp_mod(dev); diff --git a/test/cmocka/src/audio/mux/mux_copy.c b/test/cmocka/src/audio/mux/mux_copy.c index 66b21b0df27c..a4c42f99e94e 100644 --- a/test/cmocka/src/audio/mux/mux_copy.c +++ b/test/cmocka/src/audio/mux/mux_copy.c @@ -191,7 +191,7 @@ static int setup_test_case(void **state) dummy_pipe = test_malloc(sizeof(*dummy_pipe)); if (!dummy_pipe) return -ENOMEM; - dummy_pipe->expect_eos = false; + dev->expect_eos = false; dev->pipeline = dummy_pipe; mod = comp_mod(dev); From 73c24d7ecfbe11031f10c59991bf31b820fd5cee Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 9 Sep 2026 11:26:18 +0300 Subject: [PATCH 7/7] vregion: allocate vregion metadata as coherent memory struct vregion is created on the DP module's own core (via vregion_create(), invoked from module_adapter_dp_heap_new()) but is read and written from other cores too - e.g. buffer_new() on the IPC core dereferences vr->lifetime.{base,size,used,ptr} for every buffer connecting to that DP module. The struct was allocated with plain rmalloc(0, ...), i.e. ordinary cached memory. Writes made by the creating core can stay in that core's D-cache indefinitely, invisible to any other core reading the same physical address, since nothing ever flushes them out. In practice this showed up as buffer_new() on the IPC core intermittently seeing an all-zero vregion (base/size/used all 0), so any allocation failed as "lifetime alloc failed ... free 0" even though the vregion had over 100KB genuinely free - the metadata core 0 read simply hadn't propagated from core 1's cache yet. Allocate the vregion metadata with SOF_MEM_FLAG_KERNEL | SOF_MEM_FLAG_COHERENT instead, matching the existing pattern used for other cross-core shared structures in this codebase (e.g. vpage.c, regions_mm.c). This returns an uncached alias, so every core sees writes immediately without needing explicit cache maintenance. Signed-off-by: Peter Ujfalusi --- 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 fb65906446fa..1653d48a9a66 100644 --- a/zephyr/lib/vregion.c +++ b/zephyr/lib/vregion.c @@ -130,8 +130,15 @@ struct vregion *vregion_create(size_t memsize) */ total_size = ALIGN_UP(memsize, CONFIG_MM_DRV_PAGE_SIZE); - /* allocate vregion metadata separately to keep it inaccessible to the user */ - vr = rmalloc(0, sizeof(*vr)); + /* + * allocate vregion metadata separately to keep it inaccessible to the + * user. The vregion is created on the DP module's own core but is + * later read/written from other cores too (e.g. buffer_new() on the + * IPC core routes through it for buffers connecting to this module), + * so it must be coherent - plain cached memory left writes from the + * creating core invisible to other cores without an explicit flush. + */ + vr = rmalloc(SOF_MEM_FLAG_KERNEL | SOF_MEM_FLAG_COHERENT, sizeof(*vr)); if (!vr) return NULL;