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