From 053828bab86fbc6b4985c7f2f921af72c5eff1d5 Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Tue, 8 Sep 2026 13:21:11 +0300 Subject: [PATCH] audio: buffers: add support for DP-to-DP component binding Binding two DP (Data Processing) scheduled components was previously rejected with IPC4_INVALID_REQUEST because both sides required a secondary ring buffer. This patch adds support for DP-to-DP component binding under a new CONFIG_DP_TO_DP_BIND Kconfig option. In a DP-to-DP connection, a single shared ring buffer is created and attached as a secondary buffer on both the source and sink sides of the intermediate comp_buffer. The upstream DP module writes directly to the ring buffer sink API, and the downstream DP module reads directly from its source API. No copying or intermediate synchronization is required during low-latency (LL) scheduling cycles. The DP module virtual memory region backing the ring buffer is refcounted so that it remains valid across component lifetimes, and audio buffer reset and free operations ensure the shared secondary buffer is not reset or freed twice. Signed-off-by: Seppo Ingalsuo --- src/audio/buffers/audio_buffer.c | 44 +++++++++++++++------ src/audio/buffers/ring_buffer.c | 14 ++++++- src/include/sof/audio/audio_buffer.h | 7 ++++ src/ipc/ipc4/helper.c | 57 +++++++++++++++++++++------- zephyr/Kconfig | 9 +++++ 5 files changed, 105 insertions(+), 26 deletions(-) diff --git a/src/audio/buffers/audio_buffer.c b/src/audio/buffers/audio_buffer.c index 1ecf8472dd65..0b0b22a191fe 100644 --- a/src/audio/buffers/audio_buffer.c +++ b/src/audio/buffers/audio_buffer.c @@ -24,8 +24,16 @@ int audio_buffer_attach_secondary_buffer(struct sof_audio_buffer *buffer, bool at_input, struct sof_audio_buffer *secondary_buffer) { +#ifdef CONFIG_DP_TO_DP_BIND + /* check per-side: allow attaching on both sides (needed for DP-to-DP) */ + if (at_input && buffer->secondary_buffer_sink) + return -EINVAL; + if (!at_input && buffer->secondary_buffer_source) + return -EINVAL; +#else if (buffer->secondary_buffer_sink || buffer->secondary_buffer_source) return -EINVAL; +#endif /* secondary buffer must share audio params with the primary buffer */ secondary_buffer->audio_stream_params = buffer->audio_stream_params; @@ -48,6 +56,19 @@ int audio_buffer_sync_secondary_buffer(struct sof_audio_buffer *buffer, size_t l struct sof_source *data_src; struct sof_sink *data_dst; +#ifdef CONFIG_DP_TO_DP_BIND + if (buffer->secondary_buffer_sink && + buffer->secondary_buffer_sink == buffer->secondary_buffer_source) { + /* + * DP-to-DP case: a single shared ring_buffer is attached on both sides. + * The source DP writes directly to the ring_buffer sink API, and the + * sink DP reads directly from the ring_buffer source API. + * No copying is needed during the LL cycle. + */ + return 0; + } +#endif + if (buffer->secondary_buffer_sink) { /* * audio_buffer sink API is shadowed, that means there's a secondary_buffer @@ -95,7 +116,12 @@ void audio_buffer_free(struct sof_audio_buffer *buffer) CORE_CHECK_STRUCT(buffer); #if CONFIG_PIPELINE_2_0 audio_buffer_free(buffer->secondary_buffer_sink); +#ifdef CONFIG_DP_TO_DP_BIND + if (buffer->secondary_buffer_source != buffer->secondary_buffer_sink) + audio_buffer_free(buffer->secondary_buffer_source); +#else audio_buffer_free(buffer->secondary_buffer_source); +#endif #endif /* CONFIG_PIPELINE_2_0 */ /* "virtual destructor": free the buffer internals and buffer memory */ buffer->ops->free(buffer); @@ -203,18 +229,14 @@ uint32_t audio_buffer_sink_get_lft(struct sof_sink *sink) return us_in_buffer; /* - * TODO, Currently there's no DP to DP connection - * >>> the code below is never accessible and won't work because of cache incoherence <<< - * - * to make DP to DP connection possible: + * NOTE: DP-to-DP connections are now supported via a single shared ring_buffer + * attached as secondary buffer on both sides of a comp_buffer. * - * 1) module data must be ALWAYS located in non cached memory alias, allowing - * cross core access to params like period (needed below) and calling - * module_get_deadline for the next module, regardless of cores the modules are - * running on - * 2) comp_buffer must be removed from all pipeline code, replaced with a generic abstract - * class audio_buffer - allowing using comp_buffer and ring_buffer without current - * "hybrid buffer" solution + * Future improvements: + * 1) module data should be in non-cached memory alias for reliable + * cross-core access to params like period and deadlines + * 2) comp_buffer should be replaced with generic audio_buffer + * throughout pipeline code (Pipeline 2.0) */ } diff --git a/src/audio/buffers/ring_buffer.c b/src/audio/buffers/ring_buffer.c index 51245e91ca40..5f51af35294e 100644 --- a/src/audio/buffers/ring_buffer.c +++ b/src/audio/buffers/ring_buffer.c @@ -86,7 +86,6 @@ static inline void ring_buffer_writeback_shared(struct ring_buffer *ring_buffer, dcache_writeback_region(ptr, size); } - /** * @brief remove the queue from the list, free memory */ @@ -101,6 +100,19 @@ static void ring_buffer_free(struct sof_audio_buffer *audio_buffer) sof_ctx_free(alloc, (__sparse_force void *)ring_buffer->_data_buffer); sof_ctx_free(alloc, ring_buffer); + +#ifdef CONFIG_DP_TO_DP_BIND + /* + * When CONFIG_DP_TO_DP_BIND is enabled, ipc_comp_connect() takes an extra vregion + * reference for each ring_buffer created from a module vregion. Drop that + * reference here and free the allocation context only when the vregion refcount + * reaches zero. + */ + if (alloc && alloc->vreg) { + if (!vregion_put(alloc->vreg)) + sof_heap_free(alloc->heap, alloc); + } +#endif } static void ring_buffer_reset(struct sof_audio_buffer *audio_buffer) diff --git a/src/include/sof/audio/audio_buffer.h b/src/include/sof/audio/audio_buffer.h index 95317e5dc964..893170edf50b 100644 --- a/src/include/sof/audio/audio_buffer.h +++ b/src/include/sof/audio/audio_buffer.h @@ -330,9 +330,16 @@ void audio_buffer_reset(struct sof_audio_buffer *buffer) if (buffer->secondary_buffer_sink && buffer->secondary_buffer_sink->ops->reset) buffer->secondary_buffer_sink->ops->reset(buffer->secondary_buffer_sink); +#ifdef CONFIG_DP_TO_DP_BIND + if (buffer->secondary_buffer_source && + buffer->secondary_buffer_source != buffer->secondary_buffer_sink && + buffer->secondary_buffer_source->ops->reset) + buffer->secondary_buffer_source->ops->reset(buffer->secondary_buffer_source); +#else if (buffer->secondary_buffer_source && buffer->secondary_buffer_source->ops->reset) buffer->secondary_buffer_source->ops->reset(buffer->secondary_buffer_source); #endif +#endif } /* Audio-buffer wrappers for the source-sink API */ diff --git a/src/ipc/ipc4/helper.c b/src/ipc/ipc4/helper.c index 6445c834a204..c2b7ee8b43bb 100644 --- a/src/ipc/ipc4/helper.c +++ b/src/ipc/ipc4/helper.c @@ -813,18 +813,22 @@ __cold int ipc4_comp_connect(struct ipc *ipc, const struct ipc4_module_bind_unbi struct mod_alloc_ctx *alloc; #if CONFIG_ZEPHYR_DP_SCHEDULER - if (source->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_DP && - sink->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_DP) { + bool src_is_dp = source->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_DP; + bool sink_is_dp = sink->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_DP; +#ifdef CONFIG_DP_TO_DP_BIND + bool dp_to_dp = src_is_dp && sink_is_dp; +#else + if (src_is_dp && sink_is_dp) { tr_err(&ipc_tr, "DP to DP binding is not supported: can't bind %x to %x", src_id, sink_id); return IPC4_INVALID_REQUEST; } - +#endif struct comp_dev *dp; - if (sink->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_DP) + if (sink_is_dp) dp = sink; - else if (source->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_DP) + else if (src_is_dp) dp = source; else dp = NULL; @@ -897,8 +901,8 @@ __cold int ipc4_comp_connect(struct ipc *ipc, const struct ipc4_module_bind_unbi * * size = 2*max(obs of source module, ibs of destination module) * (obs and ibs is single buffer size) - * in case of DP -> LL - * size = 2*ibs of destination (LL) module. DP queue will handle obs of DP module + * in case of DP -> LL or DP -> DP + * size = 2*ibs of destination module. DP queue will handle obs of DP module */ if (source->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_LL) buf_size = MAX(ibs, obs) * 2; @@ -933,12 +937,13 @@ __cold int ipc4_comp_connect(struct ipc *ipc, const struct ipc4_module_bind_unbi #if CONFIG_ZEPHYR_DP_SCHEDULER struct ring_buffer *ring_buffer = NULL; - if (sink->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_DP || - source->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_DP) { + if (src_is_dp || sink_is_dp) { struct processing_module *srcmod = comp_mod(source); struct module_data *src_module_data = &srcmod->priv; struct processing_module *dstmod = comp_mod(sink); struct module_data *dst_module_data = &dstmod->priv; + bool is_shared = audio_buffer_is_shared(&buffer->audio_buffer); + uint32_t buf_id = buf_get_id(buffer); /* * Handle cases where the size of the ring buffer depends on the @@ -950,16 +955,40 @@ __cold int ipc4_comp_connect(struct ipc *ipc, const struct ipc4_module_bind_unbi */ ring_buffer = ring_buffer_create(dp, MAX(ibs, dst_module_data->mpd.in_buff_size), MAX(obs, src_module_data->mpd.out_buff_size), - audio_buffer_is_shared(&buffer->audio_buffer), - buf_get_id(buffer)); + is_shared, buf_id); if (!ring_buffer) { buffer_free(buffer); return IPC4_OUT_OF_MEMORY; } - /* data destination module needs to use ring_buffer */ - audio_buffer_attach_secondary_buffer(&buffer->audio_buffer, dp == source, - &ring_buffer->audio_buffer); +#ifdef CONFIG_DP_TO_DP_BIND + /* + * When CONFIG_DP_TO_DP_BIND is enabled, keep the DP module vregion + * alive for the lifetime of this ring_buffer (dropped in ring_buffer_free()). + */ + if (ring_buffer->audio_buffer.alloc) + vregion_get(ring_buffer->audio_buffer.alloc->vreg); +#endif + +#ifdef CONFIG_DP_TO_DP_BIND + if (dp_to_dp) { + /* + * DP-to-DP binding: both source and sink are DP modules. + * A single shared ring_buffer is attached on both sides + * of the comp_buffer, so source DP writes directly to it + * and sink DP reads directly from it without copying. + */ + audio_buffer_attach_secondary_buffer(&buffer->audio_buffer, true, + &ring_buffer->audio_buffer); + audio_buffer_attach_secondary_buffer(&buffer->audio_buffer, false, + &ring_buffer->audio_buffer); + } else +#endif + { + /* data destination module needs to use ring_buffer */ + audio_buffer_attach_secondary_buffer(&buffer->audio_buffer, dp == source, + &ring_buffer->audio_buffer); + } } #endif /* CONFIG_ZEPHYR_DP_SCHEDULER */ diff --git a/zephyr/Kconfig b/zephyr/Kconfig index 651b1ec16775..253869f0a773 100644 --- a/zephyr/Kconfig +++ b/zephyr/Kconfig @@ -267,6 +267,15 @@ config ZEPHYR_DP_SCHEDULER_MIN_STACK_SIZE ext_init payload. If the stack size requested in the IPC is smaller than this, then the value defined here takes over. +config DP_TO_DP_BIND + bool "Support DP to DP component binding" + default y + depends on ZEPHYR_DP_SCHEDULER + help + Enable binding between two Data Processing (DP) scheduled components. + This allows connecting DP modules together (e.g. DP source to DP sink) + via intermediate buffering. + config CROSS_CORE_STREAM bool "Enable cross-core connected pipelines" default y if IPC_MAJOR_4