Skip to content

Commit 229c19d

Browse files
committed
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 <seppo.ingalsuo@linux.intel.com>
1 parent 158518d commit 229c19d

5 files changed

Lines changed: 104 additions & 26 deletions

File tree

‎src/audio/buffers/audio_buffer.c‎

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,16 @@
2424
int audio_buffer_attach_secondary_buffer(struct sof_audio_buffer *buffer, bool at_input,
2525
struct sof_audio_buffer *secondary_buffer)
2626
{
27+
#ifdef CONFIG_DP_TO_DP_BIND
28+
/* check per-side: allow attaching on both sides (needed for DP-to-DP) */
29+
if (at_input && buffer->secondary_buffer_sink)
30+
return -EINVAL;
31+
if (!at_input && buffer->secondary_buffer_source)
32+
return -EINVAL;
33+
#else
2734
if (buffer->secondary_buffer_sink || buffer->secondary_buffer_source)
2835
return -EINVAL;
36+
#endif
2937

3038
/* secondary buffer must share audio params with the primary buffer */
3139
secondary_buffer->audio_stream_params = buffer->audio_stream_params;
@@ -48,6 +56,18 @@ int audio_buffer_sync_secondary_buffer(struct sof_audio_buffer *buffer, size_t l
4856
struct sof_source *data_src;
4957
struct sof_sink *data_dst;
5058

59+
#ifdef CONFIG_DP_TO_DP_BIND
60+
if (buffer->secondary_buffer_sink && buffer->secondary_buffer_source) {
61+
/*
62+
* DP-to-DP case: a single shared ring_buffer is attached on both sides.
63+
* The source DP writes directly to the ring_buffer sink API, and the
64+
* sink DP reads directly from the ring_buffer source API.
65+
* No copying is needed during the LL cycle.
66+
*/
67+
return 0;
68+
}
69+
#endif
70+
5171
if (buffer->secondary_buffer_sink) {
5272
/*
5373
* audio_buffer sink API is shadowed, that means there's a secondary_buffer
@@ -95,7 +115,12 @@ void audio_buffer_free(struct sof_audio_buffer *buffer)
95115
CORE_CHECK_STRUCT(buffer);
96116
#if CONFIG_PIPELINE_2_0
97117
audio_buffer_free(buffer->secondary_buffer_sink);
118+
#ifdef CONFIG_DP_TO_DP_BIND
119+
if (buffer->secondary_buffer_source != buffer->secondary_buffer_sink)
120+
audio_buffer_free(buffer->secondary_buffer_source);
121+
#else
98122
audio_buffer_free(buffer->secondary_buffer_source);
123+
#endif
99124
#endif /* CONFIG_PIPELINE_2_0 */
100125
/* "virtual destructor": free the buffer internals and buffer memory */
101126
buffer->ops->free(buffer);
@@ -203,18 +228,14 @@ uint32_t audio_buffer_sink_get_lft(struct sof_sink *sink)
203228
return us_in_buffer;
204229

205230
/*
206-
* TODO, Currently there's no DP to DP connection
207-
* >>> the code below is never accessible and won't work because of cache incoherence <<<
208-
*
209-
* to make DP to DP connection possible:
231+
* NOTE: DP-to-DP connections are now supported via a single shared ring_buffer
232+
* attached as secondary buffer on both sides of a comp_buffer.
210233
*
211-
* 1) module data must be ALWAYS located in non cached memory alias, allowing
212-
* cross core access to params like period (needed below) and calling
213-
* module_get_deadline for the next module, regardless of cores the modules are
214-
* running on
215-
* 2) comp_buffer must be removed from all pipeline code, replaced with a generic abstract
216-
* class audio_buffer - allowing using comp_buffer and ring_buffer without current
217-
* "hybrid buffer" solution
234+
* Future improvements:
235+
* 1) module data should be in non-cached memory alias for reliable
236+
* cross-core access to params like period and deadlines
237+
* 2) comp_buffer should be replaced with generic audio_buffer
238+
* throughout pipeline code (Pipeline 2.0)
218239
*/
219240
}
220241

‎src/audio/buffers/ring_buffer.c‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,6 @@ static inline void ring_buffer_writeback_shared(struct ring_buffer *ring_buffer,
8686
dcache_writeback_region(ptr, size);
8787
}
8888

89-
9089
/**
9190
* @brief remove the queue from the list, free memory
9291
*/
@@ -101,6 +100,19 @@ static void ring_buffer_free(struct sof_audio_buffer *audio_buffer)
101100

102101
sof_ctx_free(alloc, (__sparse_force void *)ring_buffer->_data_buffer);
103102
sof_ctx_free(alloc, ring_buffer);
103+
104+
#ifdef CONFIG_DP_TO_DP_BIND
105+
/*
106+
* When CONFIG_DP_TO_DP_BIND is enabled, ipc_comp_connect() takes an extra vregion
107+
* reference for each ring_buffer created from a module vregion. Drop that
108+
* reference here and free the allocation context only when the vregion refcount
109+
* reaches zero.
110+
*/
111+
if (alloc && alloc->vreg) {
112+
if (!vregion_put(alloc->vreg))
113+
rfree(alloc);
114+
}
115+
#endif
104116
}
105117

106118
static void ring_buffer_reset(struct sof_audio_buffer *audio_buffer)

‎src/include/sof/audio/audio_buffer.h‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,9 +330,16 @@ void audio_buffer_reset(struct sof_audio_buffer *buffer)
330330
if (buffer->secondary_buffer_sink && buffer->secondary_buffer_sink->ops->reset)
331331
buffer->secondary_buffer_sink->ops->reset(buffer->secondary_buffer_sink);
332332

333+
#ifdef CONFIG_DP_TO_DP_BIND
334+
if (buffer->secondary_buffer_source &&
335+
buffer->secondary_buffer_source != buffer->secondary_buffer_sink &&
336+
buffer->secondary_buffer_source->ops->reset)
337+
buffer->secondary_buffer_source->ops->reset(buffer->secondary_buffer_source);
338+
#else
333339
if (buffer->secondary_buffer_source && buffer->secondary_buffer_source->ops->reset)
334340
buffer->secondary_buffer_source->ops->reset(buffer->secondary_buffer_source);
335341
#endif
342+
#endif
336343
}
337344

338345
/* Audio-buffer wrappers for the source-sink API */

‎src/ipc/ipc4/helper.c‎

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -813,18 +813,22 @@ __cold int ipc4_comp_connect(struct ipc *ipc, const struct ipc4_module_bind_unbi
813813
struct mod_alloc_ctx *alloc;
814814

815815
#if CONFIG_ZEPHYR_DP_SCHEDULER
816-
if (source->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_DP &&
817-
sink->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_DP) {
816+
bool src_is_dp = source->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_DP;
817+
bool sink_is_dp = sink->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_DP;
818+
#ifdef CONFIG_DP_TO_DP_BIND
819+
bool dp_to_dp = src_is_dp && sink_is_dp;
820+
#else
821+
if (src_is_dp && sink_is_dp) {
818822
tr_err(&ipc_tr, "DP to DP binding is not supported: can't bind %x to %x",
819823
src_id, sink_id);
820824
return IPC4_INVALID_REQUEST;
821825
}
822-
826+
#endif
823827
struct comp_dev *dp;
824828

825-
if (sink->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_DP)
829+
if (sink_is_dp)
826830
dp = sink;
827-
else if (source->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_DP)
831+
else if (src_is_dp)
828832
dp = source;
829833
else
830834
dp = NULL;
@@ -897,8 +901,8 @@ __cold int ipc4_comp_connect(struct ipc *ipc, const struct ipc4_module_bind_unbi
897901
*
898902
* size = 2*max(obs of source module, ibs of destination module)
899903
* (obs and ibs is single buffer size)
900-
* in case of DP -> LL
901-
* size = 2*ibs of destination (LL) module. DP queue will handle obs of DP module
904+
* in case of DP -> LL or DP -> DP
905+
* size = 2*ibs of destination module. DP queue will handle obs of DP module
902906
*/
903907
if (source->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_LL)
904908
buf_size = MAX(ibs, obs) * 2;
@@ -933,12 +937,13 @@ __cold int ipc4_comp_connect(struct ipc *ipc, const struct ipc4_module_bind_unbi
933937
#if CONFIG_ZEPHYR_DP_SCHEDULER
934938
struct ring_buffer *ring_buffer = NULL;
935939

936-
if (sink->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_DP ||
937-
source->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_DP) {
940+
if (src_is_dp || sink_is_dp) {
938941
struct processing_module *srcmod = comp_mod(source);
939942
struct module_data *src_module_data = &srcmod->priv;
940943
struct processing_module *dstmod = comp_mod(sink);
941944
struct module_data *dst_module_data = &dstmod->priv;
945+
bool is_shared = audio_buffer_is_shared(&buffer->audio_buffer);
946+
uint32_t buf_id = buf_get_id(buffer);
942947

943948
/*
944949
* 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
950955
*/
951956
ring_buffer = ring_buffer_create(dp, MAX(ibs, dst_module_data->mpd.in_buff_size),
952957
MAX(obs, src_module_data->mpd.out_buff_size),
953-
audio_buffer_is_shared(&buffer->audio_buffer),
954-
buf_get_id(buffer));
958+
is_shared, buf_id);
955959
if (!ring_buffer) {
956960
buffer_free(buffer);
957961
return IPC4_OUT_OF_MEMORY;
958962
}
959963

960-
/* data destination module needs to use ring_buffer */
961-
audio_buffer_attach_secondary_buffer(&buffer->audio_buffer, dp == source,
962-
&ring_buffer->audio_buffer);
964+
#ifdef CONFIG_DP_TO_DP_BIND
965+
/*
966+
* When CONFIG_DP_TO_DP_BIND is enabled, keep the DP module vregion
967+
* alive for the lifetime of this ring_buffer (dropped in ring_buffer_free()).
968+
*/
969+
if (ring_buffer->audio_buffer.alloc)
970+
vregion_get(ring_buffer->audio_buffer.alloc->vreg);
971+
#endif
972+
973+
#ifdef CONFIG_DP_TO_DP_BIND
974+
if (dp_to_dp) {
975+
/*
976+
* DP-to-DP binding: both source and sink are DP modules.
977+
* A single shared ring_buffer is attached on both sides
978+
* of the comp_buffer, so source DP writes directly to it
979+
* and sink DP reads directly from it without copying.
980+
*/
981+
audio_buffer_attach_secondary_buffer(&buffer->audio_buffer, true,
982+
&ring_buffer->audio_buffer);
983+
audio_buffer_attach_secondary_buffer(&buffer->audio_buffer, false,
984+
&ring_buffer->audio_buffer);
985+
} else
986+
#endif
987+
{
988+
/* data destination module needs to use ring_buffer */
989+
audio_buffer_attach_secondary_buffer(&buffer->audio_buffer, dp == source,
990+
&ring_buffer->audio_buffer);
991+
}
963992
}
964993

965994
#endif /* CONFIG_ZEPHYR_DP_SCHEDULER */

‎zephyr/Kconfig‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,15 @@ config ZEPHYR_DP_SCHEDULER_MIN_STACK_SIZE
267267
ext_init payload. If the stack size requested in the IPC is
268268
smaller than this, then the value defined here takes over.
269269

270+
config DP_TO_DP_BIND
271+
bool "Support DP to DP component binding"
272+
default y
273+
depends on ZEPHYR_DP_SCHEDULER
274+
help
275+
Enable binding between two Data Processing (DP) scheduled components.
276+
This allows connecting DP modules together (e.g. DP source to DP sink)
277+
via intermediate buffering.
278+
270279
config CROSS_CORE_STREAM
271280
bool "Enable cross-core connected pipelines"
272281
default y if IPC_MAJOR_4

0 commit comments

Comments
 (0)