From 3292b73cdb337b2860a55b207cb0b256d552ad76 Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Tue, 20 Feb 2024 15:32:25 +0200 Subject: [PATCH 01/18] platform: library: Add missing include task.h to ll_schedule MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes build issues In file included from tools/testbench/common_test.c:5: src/platform/library/include/platform/lib/ll_schedule.h:22:51: error: ‘enum task_state’ declared inside parameter list will not be visible outside of this definition or declaration [-Werror] In file included from tools/testbench/common_test.c:16: tools/testbench/build_testbench/sof_ep/install/include/sof/ schedule/ll_schedule.h:40:5: error: conflicting types for ‘schedule_task_init_ll’ Signed-off-by: Seppo Ingalsuo --- src/platform/library/include/platform/lib/ll_schedule.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/platform/library/include/platform/lib/ll_schedule.h b/src/platform/library/include/platform/lib/ll_schedule.h index be79ccf15d22..06c918b84fdc 100644 --- a/src/platform/library/include/platform/lib/ll_schedule.h +++ b/src/platform/library/include/platform/lib/ll_schedule.h @@ -6,6 +6,7 @@ #ifndef __LIBRARY_INCLUDE_LIB_SCHEDULE_H__ #define __LIBRARY_INCLUDE_LIB_SCHEDULE_H__ +#include #include struct task; From be9f07512c69002468856d95553d2eb14d5adbc8 Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Tue, 10 Sep 2024 16:15:59 +0300 Subject: [PATCH 02/18] Lib: Dai-legacy: Add chmap and channel_copy members to struct dai_data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This avoids compile error in copier.c: sof/src/audio/copier/copier.c:692:34: error: ‘struct dai_data’ has no member named ‘chmap’ and in copier-dai.c: sof/src/audio/copier/copier_dai.c:550:34: error: ‘struct dai_data’ has no member named ‘channel_copy’ Signed-off-by: Seppo Ingalsuo --- src/include/sof/lib/dai-legacy.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/include/sof/lib/dai-legacy.h b/src/include/sof/lib/dai-legacy.h index 328ac156dc05..6594c84be76a 100644 --- a/src/include/sof/lib/dai-legacy.h +++ b/src/include/sof/lib/dai-legacy.h @@ -163,6 +163,10 @@ struct llp_slot_info { uint32_t reg_offset; }; +typedef int (*channel_copy_func)(const struct audio_stream *src, unsigned int src_channel, + struct audio_stream *dst, unsigned int dst_channel, + unsigned int frames); + /** * \brief DAI runtime data */ @@ -181,6 +185,11 @@ struct dai_data { int xrun; /* true if we are doing xrun recovery */ pcm_converter_func process; /* processing function */ + uint32_t chmap; + channel_copy_func channel_copy; /* channel copy func used by multi-endpoint + * gateway to mux/demux stream from/to multiple + * DMA buffers + */ uint32_t period_bytes; /* number of bytes per one period */ uint64_t total_data_processed; From 21b5571728f895ac4570975707c09a2e554ba7b0 Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Tue, 10 Sep 2024 16:19:09 +0300 Subject: [PATCH 03/18] IPC4: Add missing header include to alh.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The use of size_t needs include of stddef.h. Without this there is build error in copier_hifi.c: sof/src/include/ipc4/alh.h:70:15: error: unknown type name ‘size_t’ Signed-off-by: Seppo Ingalsuo --- src/include/ipc4/alh.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/include/ipc4/alh.h b/src/include/ipc4/alh.h index c9658c225d76..eaa6692210c3 100644 --- a/src/include/ipc4/alh.h +++ b/src/include/ipc4/alh.h @@ -26,6 +26,7 @@ #include #include +#include #include #define IPC4_ALH_MAX_NUMBER_OF_GTW 16 From 7bf5d3da31f62e25a0bcf0f0617128a89ae4a682 Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Mon, 19 Feb 2024 17:11:55 +0200 Subject: [PATCH 04/18] IPC: IPC4: Skip ipc_wait_for_compound_msg() for CONFIG_LIBRARY This prevents the IPC4_FAILURE since there is no parallel execution in scheduler and pipelines with this IPC request and respond step. Signed-off-by: Seppo Ingalsuo --- src/ipc/ipc4/handler.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/ipc/ipc4/handler.c b/src/ipc/ipc4/handler.c index ff3ad559a0a6..72e01639aa27 100644 --- a/src/ipc/ipc4/handler.c +++ b/src/ipc/ipc4/handler.c @@ -534,6 +534,17 @@ static void ipc_compound_msg_done(uint32_t msg_id, int error) } } +#if CONFIG_LIBRARY +/* There is no parallel execution in testbench for scheduler and pipelines, so the result would + * be always IPC4_FAILURE. Therefore the compound messages handling is simplified. The pipeline + * triggers will require an explicit scheduler call to get the components to desired state. + */ +static int ipc_wait_for_compound_msg(void) +{ + atomic_set(&msg_data.delayed_reply, 0); + return IPC4_SUCCESS; +} +#else static int ipc_wait_for_compound_msg(void) { int try_count = 30; @@ -550,6 +561,7 @@ static int ipc_wait_for_compound_msg(void) return IPC4_SUCCESS; } +#endif const struct ipc4_pipeline_set_state_data *ipc4_get_pipeline_data_wrapper(void) { From bbd3865dbd9602a4ecdb5ac670ca8809ae8a02e7 Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Mon, 16 Sep 2024 18:10:25 +0300 Subject: [PATCH 05/18] IPC: IPC4: Initialize IPC reply to zero ipc_cmd() This change fixes a Valgrind reported issue of use of uninitialized data. Signed-off-by: Seppo Ingalsuo --- src/ipc/ipc4/handler.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ipc/ipc4/handler.c b/src/ipc/ipc4/handler.c index 72e01639aa27..3e9b5d1b4472 100644 --- a/src/ipc/ipc4/handler.c +++ b/src/ipc/ipc4/handler.c @@ -1615,7 +1615,7 @@ void ipc_cmd(struct ipc_cmd_hdr *_hdr) /* FW sends an ipc message to host if request bit is clear */ if (in->primary.r.rsp == SOF_IPC4_MESSAGE_DIR_MSG_REQUEST) { struct ipc *ipc = ipc_get(); - struct ipc4_message_reply reply; + struct ipc4_message_reply reply = {{0}}; /* Process flow and time stamp for IPC4 msg processed on secondary core : * core 0 (primary core) core x (secondary core) From 634d1928d299af0c756b8e029b3199a50b0b7d57 Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Tue, 17 Sep 2024 20:19:23 +0300 Subject: [PATCH 06/18] IPC: IPC4: Remove unused function process_dma_index() Warnings are errors in testbench build. This change avoids error: sof/src/ipc/ipc4/helper.c:761:19: error: unused function 'process_dma_index' [-Werror,-Wunused-function] Signed-off-by: Seppo Ingalsuo --- src/ipc/ipc4/helper.c | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/src/ipc/ipc4/helper.c b/src/ipc/ipc4/helper.c index c7798861a518..7f0e292b4cbd 100644 --- a/src/ipc/ipc4/helper.c +++ b/src/ipc/ipc4/helper.c @@ -755,27 +755,6 @@ int ipc_comp_disconnect(struct ipc *ipc, ipc_pipe_comp_connect *_connect) return IPC4_SUCCESS; } -/* dma index may be for playback or capture. Current hw supports PLATFORM_MAX_DMA_CHAN playback - * channels and the rest are for capture. This function converts DMA ID to DMA channel. - */ -static inline int process_dma_index(uint32_t dma_id, uint32_t *dir, uint32_t *chan) -{ - if (dma_id > DAI_NUM_HDA_OUT + DAI_NUM_HDA_IN) { - tr_err(&ipc_tr, "dma id %d is out of range", dma_id); - return IPC4_INVALID_NODE_ID; - } - - if (dma_id >= PLATFORM_MAX_DMA_CHAN) { - *dir = SOF_IPC_STREAM_CAPTURE; - *chan = dma_id - PLATFORM_MAX_DMA_CHAN; - } else { - *dir = SOF_IPC_STREAM_PLAYBACK; - *chan = dma_id; - } - - return IPC4_SUCCESS; -} - #if CONFIG_COMP_CHAIN_DMA int ipc4_chain_manager_create(struct ipc4_chain_dma *cdma) { From 77616b68ca2da15612e52a7d2848a21e251323b1 Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Tue, 17 Sep 2024 17:15:56 +0300 Subject: [PATCH 07/18] IPC: IPC4: Add File and dcblock to UUID map This addition allows to load file components for DAI and host copier replacement for testbench. DC blocker is added as process component to allow testing of this component as example. Support for other processing components will need a new solution to pass the UUIDs over IPC messages instead of the 8-bit index value. Signed-off-by: Seppo Ingalsuo --- src/ipc/ipc4/helper.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/ipc/ipc4/helper.c b/src/ipc/ipc4/helper.c index 7f0e292b4cbd..0a8549c65b0f 100644 --- a/src/ipc/ipc4/helper.c +++ b/src/ipc/ipc4/helper.c @@ -961,6 +961,16 @@ static const struct ipc4_module_uuid uuid_map[] = { .d = { 0xa0, 0x8f, 0x97, 0xfc, 0xc4, 0x2e, 0xaa, 0xeb }}}, /* ALSA aplay */ {0x99, {.a = 0x66def9f0, .b = 0x39f2, .c = 0x11ed, .d = { 0xf7, 0x89, 0xaf, 0x98, 0xa6, 0x44, 0x0c, 0xc4 }}}, /* ALSA arecord */ + {0x9a, {.a = 0xbfc7488c, .b = 0x75aa, .c = 0x4ce8, + .d = { 0x9d, 0xbe, 0xd8, 0xda, 0x08, 0xa6, 0x98, 0xc2 }}}, /* file read aif */ + {0x9b, {.a = 0xbfc7488c, .b = 0x75aa, .c = 0x4ce8, + .d = { 0x9d, 0xbe, 0xd8, 0xda, 0x08, 0xa6, 0x98, 0xc2 }}}, /* file write aif */ + {0x9c, {.a = 0xbfc7488c, .b = 0x75aa, .c = 0x4ce8, + .d = { 0x9d, 0xbe, 0xd8, 0xda, 0x08, 0xa6, 0x98, 0xc2 }}}, /* file read dai */ + {0x9d, {.a = 0xbfc7488c, .b = 0x75aa, .c = 0x4ce8, + .d = { 0x9d, 0xbe, 0xd8, 0xda, 0x08, 0xa6, 0x98, 0xc2 }}}, /* file write dai */ + {0x9e, {.a = 0xb809efaf, .b = 0x5681, .c = 0x42b1, + .d = { 0x9e, 0xd6, 0x04, 0xbb, 0x01, 0x2d, 0xd3, 0x84 }}}, /* process: dcblock */ }; static const struct comp_driver *ipc4_library_get_drv(int module_id) From 3c3ebc4e0861c42c5fd0a3cb2aecdee2e1b2f310 Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Tue, 17 Sep 2024 17:20:30 +0300 Subject: [PATCH 08/18] Audio: Component: Add module headers to component.h for testbench Components initialize in testbench needs these headers. Adding these allows load of to the components when found in topology. Signed-off-by: Seppo Ingalsuo --- src/include/sof/audio/component.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/include/sof/audio/component.h b/src/include/sof/audio/component.h index 1463da2e2c61..81eb403ef6b1 100644 --- a/src/include/sof/audio/component.h +++ b/src/include/sof/audio/component.h @@ -802,6 +802,7 @@ void sys_comp_host_init(void); void sys_comp_kpb_init(void); void sys_comp_selector_init(void); +void sys_comp_module_copier_interface_init(void); void sys_comp_module_crossover_interface_init(void); void sys_comp_module_dcblock_interface_init(void); void sys_comp_module_demux_interface_init(void); @@ -809,15 +810,19 @@ void sys_comp_module_drc_interface_init(void); void sys_comp_module_dts_interface_init(void); void sys_comp_module_eq_fir_interface_init(void); void sys_comp_module_eq_iir_interface_init(void); +void sys_comp_module_gain_interface_init(void); void sys_comp_module_google_rtc_audio_processing_interface_init(void); void sys_comp_module_google_ctc_audio_processing_interface_init(void); void sys_comp_module_igo_nr_interface_init(void); void sys_comp_module_mfcc_interface_init(void); void sys_comp_module_mixer_interface_init(void); +void sys_comp_module_mixin_interface_init(void); +void sys_comp_module_mixout_interface_init(void); void sys_comp_module_multiband_drc_interface_init(void); void sys_comp_module_mux_interface_init(void); void sys_comp_module_asrc_interface_init(void); void sys_comp_module_rtnr_interface_init(void); +void sys_comp_module_selector_interface_init(void); void sys_comp_module_src_interface_init(void); void sys_comp_module_tdfb_interface_init(void); void sys_comp_module_volume_interface_init(void); From 3645e6dd32b364484ba44c7daca7da19a8585262 Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Wed, 10 Apr 2024 18:21:43 +0300 Subject: [PATCH 09/18] Audio: Crossover: Fix IPC4 testbench build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The crossover_init_output_pins() function is changed to static. This change avoids build error: sof/src/audio/crossover/crossover_ipc4.c:32:5: error: no previous prototype for ‘crossover_init_output_pins’ [-Werror=missing-prototypes] Signed-off-by: Seppo Ingalsuo --- src/audio/crossover/crossover_ipc4.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/audio/crossover/crossover_ipc4.c b/src/audio/crossover/crossover_ipc4.c index c928a5945034..9fbe7730e53e 100644 --- a/src/audio/crossover/crossover_ipc4.c +++ b/src/audio/crossover/crossover_ipc4.c @@ -29,7 +29,7 @@ int crossover_get_sink_id(struct comp_data *cd, uint32_t pipeline_id, uint32_t i * kernel know that the base_cfg_ext needs to be appended to the IPC payload. The * Extension is needed to know the output pin indices. */ -int crossover_init_output_pins(struct processing_module *mod) +static int crossover_init_output_pins(struct processing_module *mod) { struct comp_data *cd = module_get_private_data(mod); struct comp_dev *dev = mod->dev; From fe6b8d84a1a562c61eb39ffeee8710f79af43b54 Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Wed, 18 Sep 2024 12:32:48 +0300 Subject: [PATCH 10/18] Audio: TDFB: Fix flexible array members in control notification The build of testbench IPC4 with xt-clang fails with this error: sof/src/audio/tdfb/tdfb_ipc4.c:31:37: error: field 'module_data' with variable sized type 'struct sof_ipc4_notify_module_data' not at the end of a struct or class is a GNU extension [-Werror,-Wgnu-variable-sized-type-not-at-end] sof/src/audio/tdfb/tdfb_ipc4.c:32:38: error: field 'control_msg' with variable sized type 'struct sof_ipc4_control_msg_payload' not at the end of a struct or class is a GNU extension [-Werror,-Wgnu-variable-sized-type-not-at-end] The issue is fixed with use of struct pointers to IPC message msg_module_data and msg_payload to prepare the message template. Signed-off-by: Seppo Ingalsuo --- src/audio/tdfb/tdfb_ipc4.c | 41 +++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/src/audio/tdfb/tdfb_ipc4.c b/src/audio/tdfb/tdfb_ipc4.c index 7a16328d952d..176d9fa710b5 100644 --- a/src/audio/tdfb/tdfb_ipc4.c +++ b/src/audio/tdfb/tdfb_ipc4.c @@ -27,12 +27,6 @@ #include "tdfb.h" #include "tdfb_comp.h" -struct tdfb_notification_payload { - struct sof_ipc4_notify_module_data module_data; - struct sof_ipc4_control_msg_payload control_msg; - struct sof_ipc4_ctrl_value_chan control_value; /* One channel value */ -}; - LOG_MODULE_DECLARE(tdfb, CONFIG_SOF_LOG_LEVEL); static struct ipc_msg *tdfb_notification_init(struct processing_module *mod, @@ -44,8 +38,9 @@ static struct ipc_msg *tdfb_notification_init(struct processing_module *mod, struct comp_ipc_config *ipc_config = &dev->ipc_config; union ipc4_notification_header *primary = (union ipc4_notification_header *)&msg_proto.header; + struct sof_ipc4_notify_module_data *msg_module_data; + struct sof_ipc4_control_msg_payload *msg_payload; struct ipc_msg *msg; - struct tdfb_notification_payload *payload; /* Clear header, extension, and other ipc_msg members */ memset_s(&msg_proto, sizeof(msg_proto), 0, sizeof(msg_proto)); @@ -54,32 +49,38 @@ static struct ipc_msg *tdfb_notification_init(struct processing_module *mod, primary->r.rsp = SOF_IPC4_MESSAGE_DIR_MSG_REQUEST; primary->r.msg_tgt = SOF_IPC4_MESSAGE_TARGET_FW_GEN_MSG; msg = ipc_msg_w_ext_init(msg_proto.header, msg_proto.extension, - sizeof(struct tdfb_notification_payload)); + sizeof(struct sof_ipc4_notify_module_data) + + sizeof(struct sof_ipc4_control_msg_payload) + + sizeof(struct sof_ipc4_ctrl_value_chan)); if (!msg) return NULL; - payload = (struct tdfb_notification_payload *)msg->tx_data; - payload->module_data.instance_id = IPC4_INST_ID(ipc_config->id); - payload->module_data.module_id = IPC4_MOD_ID(ipc_config->id); - payload->module_data.event_id = SOF_IPC4_NOTIFY_MODULE_EVENTID_ALSA_MAGIC_VAL | + msg_module_data = (struct sof_ipc4_notify_module_data *)msg->tx_data; + msg_module_data->instance_id = IPC4_INST_ID(ipc_config->id); + msg_module_data->module_id = IPC4_MOD_ID(ipc_config->id); + msg_module_data->event_id = SOF_IPC4_NOTIFY_MODULE_EVENTID_ALSA_MAGIC_VAL | control_type_param_id; - payload->module_data.event_data_size = sizeof(struct sof_ipc4_control_msg_payload) + + msg_module_data->event_data_size = sizeof(struct sof_ipc4_control_msg_payload) + sizeof(struct sof_ipc4_ctrl_value_chan); - payload->control_msg.id = control_id; - payload->control_msg.num_elems = 1; - payload->control_value.channel = 0; + + msg_payload = (struct sof_ipc4_control_msg_payload *)msg_module_data->event_data; + msg_payload->id = control_id; + msg_payload->num_elems = 1; + msg_payload->chanv[0].channel = 0; comp_dbg(dev, "instance_id = 0x%08x, module_id = 0x%08x", - payload->module_data.instance_id, payload->module_data.module_id); + msg_module_data->instance_id, msg_module_data->module_id); return msg; } static void tdfb_send_notification(struct ipc_msg *msg, uint32_t val) { - struct tdfb_notification_payload *ipc_payload; + struct sof_ipc4_notify_module_data *msg_module_data; + struct sof_ipc4_control_msg_payload *msg_payload; - ipc_payload = (struct tdfb_notification_payload *)msg->tx_data; - ipc_payload->control_value.value = val; + msg_module_data = (struct sof_ipc4_notify_module_data *)msg->tx_data; + msg_payload = (struct sof_ipc4_control_msg_payload *)msg_module_data->event_data; + msg_payload->chanv[0].value = val; ipc_msg_send(msg, NULL, false); } From ea2fdda9463b4b4b5fda3c08dedff1b3ac17e717 Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Wed, 18 Sep 2024 12:38:53 +0300 Subject: [PATCH 11/18] Audio: Volume: Remove two unused variables from peakvolume The remove of variables in1 and out1 in peakvolume HiFi4 function vol_s16_to_s16() avoids testbench IPC4 build error: sof/src/audio/volume/volume_hifi4_with_peakvol.c:406:10: error: unused variable 'in1' [-Werror,-Wunused-variable] sof/src/audio/volume/volume_hifi4_with_peakvol.c:407:10: error: unused variable 'out1' [-Werror,-Wunused-variable] Signed-off-by: Seppo Ingalsuo --- src/audio/volume/volume_hifi4_with_peakvol.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/audio/volume/volume_hifi4_with_peakvol.c b/src/audio/volume/volume_hifi4_with_peakvol.c index 9dfac0bdb0f1..d30f64ce47a3 100644 --- a/src/audio/volume/volume_hifi4_with_peakvol.c +++ b/src/audio/volume/volume_hifi4_with_peakvol.c @@ -403,8 +403,6 @@ static void vol_s16_to_s16(struct processing_module *mod, struct input_stream_bu + bsource->consumed); ae_f16x4 *out = (ae_f16x4 *)audio_stream_wrap(sink, (char *)audio_stream_get_wptr(sink) + bsink->size); - ae_f16 *in1; - ae_f16 *out1; const int channels_count = audio_stream_get_channels(sink); const int inc = sizeof(ae_f32x2); int samples = channels_count * frames; From e3f13a861b006ec4e9361189d5ac7b5b306f59a7 Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Tue, 17 Sep 2024 18:07:11 +0300 Subject: [PATCH 12/18] Tools: Testbench: Rename files with IPC3 This change prepares to add IPC4 support to testbench. Signed-off-by: Seppo Ingalsuo --- tools/testbench/CMakeLists.txt | 4 ++-- tools/testbench/{common_test.c => common_test_ipc3.c} | 0 tools/testbench/{topology.c => topology_ipc3.c} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename tools/testbench/{common_test.c => common_test_ipc3.c} (100%) rename tools/testbench/{topology.c => topology_ipc3.c} (100%) diff --git a/tools/testbench/CMakeLists.txt b/tools/testbench/CMakeLists.txt index 8e95a60d6a14..599542f53ede 100644 --- a/tools/testbench/CMakeLists.txt +++ b/tools/testbench/CMakeLists.txt @@ -11,9 +11,9 @@ set(default_asoc_h "/usr/include/alsa/sound/uapi/asoc.h") add_executable(testbench testbench.c - common_test.c + common_test_ipc3.c file.c - topology.c + topology_ipc3.c ) sof_append_relative_path_definitions(testbench) diff --git a/tools/testbench/common_test.c b/tools/testbench/common_test_ipc3.c similarity index 100% rename from tools/testbench/common_test.c rename to tools/testbench/common_test_ipc3.c diff --git a/tools/testbench/topology.c b/tools/testbench/topology_ipc3.c similarity index 100% rename from tools/testbench/topology.c rename to tools/testbench/topology_ipc3.c From c7b090a658fc0ff21723495050658f884d6b2810 Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Tue, 17 Sep 2024 18:17:27 +0300 Subject: [PATCH 13/18] Tools: Testbench: Clean up header files Prefix macros with TB and add TESTBENCH to headers single time include control macros. Especially ifndef _TRACE_H is in risk to conflict with possible other headers. Signed-off-by: Seppo Ingalsuo --- tools/testbench/common_test_ipc3.c | 2 +- .../testbench/include/testbench/common_test.h | 22 +++++++-------- tools/testbench/include/testbench/trace.h | 6 ++-- tools/testbench/testbench.c | 28 +++++++++---------- 4 files changed, 29 insertions(+), 29 deletions(-) diff --git a/tools/testbench/common_test_ipc3.c b/tools/testbench/common_test_ipc3.c index f4481b38af02..f43b1f36750f 100644 --- a/tools/testbench/common_test_ipc3.c +++ b/tools/testbench/common_test_ipc3.c @@ -204,7 +204,7 @@ int tb_pipeline_params(struct testbench_prm *tp, struct ipc *ipc, struct pipelin { struct comp_dev *cd; struct sof_ipc_pcm_params params = {{0}}; - char message[DEBUG_MSG_LEN]; + char message[TB_DEBUG_MSG_LEN]; int fs_period; int period; int ret = 0; diff --git a/tools/testbench/include/testbench/common_test.h b/tools/testbench/include/testbench/common_test.h index 4960c13e2d02..40e7b38b1216 100644 --- a/tools/testbench/include/testbench/common_test.h +++ b/tools/testbench/include/testbench/common_test.h @@ -3,8 +3,8 @@ * Copyright(c) 2018 Intel Corporation. All rights reserved. */ -#ifndef _COMMON_TEST_H -#define _COMMON_TEST_H +#ifndef _TESTBENCH_COMMON_TEST_H +#define _TESTBENCH_COMMON_TEST_H #include #include @@ -18,14 +18,14 @@ #include -#define DEBUG_MSG_LEN 1024 -#define MAX_LIB_NAME_LEN 1024 +#define TB_DEBUG_MSG_LEN 1024 +#define TB_MAX_LIB_NAME_LEN 1024 -#define MAX_INPUT_FILE_NUM 16 -#define MAX_OUTPUT_FILE_NUM 16 +#define TB_MAX_INPUT_FILE_NUM 16 +#define TB_MAX_OUTPUT_FILE_NUM 16 /* number of widgets types supported in testbench */ -#define NUM_WIDGETS_SUPPORTED 16 +#define TB_NUM_WIDGETS_SUPPORTED 16 struct tplg_context; @@ -38,12 +38,12 @@ struct tplg_context; struct testbench_prm { long long total_cycles; char *tplg_file; /* topology file to use */ - char *input_file[MAX_INPUT_FILE_NUM]; /* input file names */ - char *output_file[MAX_OUTPUT_FILE_NUM]; /* output file names */ + char *input_file[TB_MAX_INPUT_FILE_NUM]; /* input file names */ + char *output_file[TB_MAX_OUTPUT_FILE_NUM]; /* output file names */ int input_file_num; /* number of input files */ int output_file_num; /* number of output files */ char *bits_in; /* input bit format */ - int pipelines[MAX_OUTPUT_FILE_NUM]; /* output file names */ + int pipelines[TB_MAX_OUTPUT_FILE_NUM]; /* output file names */ int pipeline_num; struct tplg_context *ctx; @@ -105,4 +105,4 @@ void tb_gettime(struct timespec *td); void tb_getcycles(uint64_t *cycles); -#endif +#endif /* _TESTBENCH_COMMON_TEST_H */ diff --git a/tools/testbench/include/testbench/trace.h b/tools/testbench/include/testbench/trace.h index 66e63e217adf..50b4863e4346 100644 --- a/tools/testbench/include/testbench/trace.h +++ b/tools/testbench/include/testbench/trace.h @@ -8,9 +8,9 @@ #include -#ifndef _TRACE_H -#define _TRACE_H +#ifndef _TESTBENCH_TRACE_H +#define _TESTBENCH_TRACE_H void tb_enable_trace(unsigned int log_level); -#endif +#endif /* _TESTBENCH_TRACE_H */ diff --git a/tools/testbench/testbench.c b/tools/testbench/testbench.c index 0f464d577f1f..60f80853483c 100644 --- a/tools/testbench/testbench.c +++ b/tools/testbench/testbench.c @@ -33,7 +33,7 @@ static int parse_output_files(char *outputs, struct testbench_prm *tp) char *token = strtok_r(outputs, ",", &output_token); int index; - for (index = 0; index < MAX_OUTPUT_FILE_NUM && token; index++) { + for (index = 0; index < TB_MAX_OUTPUT_FILE_NUM && token; index++) { /* get output file name with current index */ tp->output_file[index] = strdup(token); @@ -41,10 +41,10 @@ static int parse_output_files(char *outputs, struct testbench_prm *tp) token = strtok_r(NULL, ",", &output_token); } - if (index == MAX_OUTPUT_FILE_NUM && token) { + if (index == TB_MAX_OUTPUT_FILE_NUM && token) { fprintf(stderr, "error: max output file number is %d\n", - MAX_OUTPUT_FILE_NUM); - for (index = 0; index < MAX_OUTPUT_FILE_NUM; index++) + TB_MAX_OUTPUT_FILE_NUM); + for (index = 0; index < TB_MAX_OUTPUT_FILE_NUM; index++) free(tp->output_file[index]); return -EINVAL; } @@ -63,7 +63,7 @@ static int parse_input_files(char *inputs, struct testbench_prm *tp) char *token = strtok_r(inputs, ",", &input_token); int index; - for (index = 0; index < MAX_INPUT_FILE_NUM && token; index++) { + for (index = 0; index < TB_MAX_INPUT_FILE_NUM && token; index++) { /* get input file name with current index */ tp->input_file[index] = strdup(token); @@ -71,10 +71,10 @@ static int parse_input_files(char *inputs, struct testbench_prm *tp) token = strtok_r(NULL, ",", &input_token); } - if (index == MAX_INPUT_FILE_NUM && token) { + if (index == TB_MAX_INPUT_FILE_NUM && token) { fprintf(stderr, "error: max input file number is %d\n", - MAX_INPUT_FILE_NUM); - for (index = 0; index < MAX_INPUT_FILE_NUM; index++) + TB_MAX_INPUT_FILE_NUM); + for (index = 0; index < TB_MAX_INPUT_FILE_NUM; index++) free(tp->input_file[index]); return -EINVAL; } @@ -90,7 +90,7 @@ static int parse_pipelines(char *pipelines, struct testbench_prm *tp) char *token = strtok_r(pipelines, ",", &output_token); int index; - for (index = 0; index < MAX_OUTPUT_FILE_NUM && token; index++) { + for (index = 0; index < TB_MAX_OUTPUT_FILE_NUM && token; index++) { /* get output file name with current index */ tp->pipelines[index] = atoi(token); @@ -98,9 +98,9 @@ static int parse_pipelines(char *pipelines, struct testbench_prm *tp) token = strtok_r(NULL, ",", &output_token); } - if (index == MAX_OUTPUT_FILE_NUM && token) { + if (index == TB_MAX_OUTPUT_FILE_NUM && token) { fprintf(stderr, "error: max output file number is %d\n", - MAX_OUTPUT_FILE_NUM); + TB_MAX_OUTPUT_FILE_NUM); return -EINVAL; } @@ -743,10 +743,10 @@ int main(int argc, char **argv) tp.tplg_file = NULL; tp.input_file_num = 0; tp.output_file_num = 0; - for (i = 0; i < MAX_OUTPUT_FILE_NUM; i++) + for (i = 0; i < TB_MAX_OUTPUT_FILE_NUM; i++) tp.output_file[i] = NULL; - for (i = 0; i < MAX_INPUT_FILE_NUM; i++) + for (i = 0; i < TB_MAX_INPUT_FILE_NUM; i++) tp.input_file[i] = NULL; tp.channels_in = TESTBENCH_NCH; @@ -755,7 +755,7 @@ int main(int argc, char **argv) tp.copy_check = false; tp.quiet = 0; tp.dynamic_pipeline_iterations = 1; - tp.pipeline_string = calloc(1, DEBUG_MSG_LEN); + tp.pipeline_string = calloc(1, TB_DEBUG_MSG_LEN); tp.pipelines[0] = 1; tp.pipeline_num = 1; tp.tick_period_us = 0; /* Execute fast non-real time, for 1 ms tick use -T 1000 */ From 8129566550afa8198e90f0513f76585992129904 Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Tue, 17 Sep 2024 18:35:31 +0300 Subject: [PATCH 14/18] Tools: Testbench: Cleanup and move common and IPC3 specific functions The helper functions are moved from testbench.c to common_test.c and common_test_ipc3.c as preparation to add IPC4 support. The file components are looked up in test setup to arrays to ease finding them in test run and control ending the test. The parse string for command getopt() is fixed to match the supported options. The testbench parameter struct is changed to dynamically allocated and zeroed by calloc(). It also avoids issues found with valgrind about uninitialized. Signed-off-by: Seppo Ingalsuo --- tools/testbench/CMakeLists.txt | 1 + tools/testbench/common_test.c | 253 +++++++++ tools/testbench/common_test_ipc3.c | 285 +++++++--- tools/testbench/file.c | 14 +- .../testbench/include/testbench/common_test.h | 72 ++- tools/testbench/testbench.c | 514 ++++-------------- tools/testbench/topology_ipc3.c | 26 +- 7 files changed, 617 insertions(+), 548 deletions(-) create mode 100644 tools/testbench/common_test.c diff --git a/tools/testbench/CMakeLists.txt b/tools/testbench/CMakeLists.txt index 599542f53ede..daccb4ccaccd 100644 --- a/tools/testbench/CMakeLists.txt +++ b/tools/testbench/CMakeLists.txt @@ -11,6 +11,7 @@ set(default_asoc_h "/usr/include/alsa/sound/uapi/asoc.h") add_executable(testbench testbench.c + common_test.c common_test_ipc3.c file.c topology_ipc3.c diff --git a/tools/testbench/common_test.c b/tools/testbench/common_test.c new file mode 100644 index 000000000000..dcbf0a353a37 --- /dev/null +++ b/tools/testbench/common_test.c @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2018-2024 Intel Corporation. All rights reserved. + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "testbench/common_test.h" +#include "testbench/trace.h" +#include "testbench/file.h" + +#if defined __XCC__ +#include +#endif + +int tb_load_topology(struct testbench_prm *tp) +{ + struct tplg_context *ctx = &tp->tplg; + int ret; + + /* setup the thread virtual core config */ + memset(ctx, 0, sizeof(*ctx)); + ctx->comp_id = 1; + ctx->core_id = 0; + ctx->sof = sof_get(); + ctx->tplg_file = tp->tplg_file; + + /* parse topology file and create pipeline */ + ret = tb_parse_topology(tp); + if (ret < 0) + fprintf(stderr, "error: parsing topology\n"); + + tb_debug_print("topology parsing complete\n"); + return ret; +} + +static int tb_find_file_helper(struct testbench_prm *tp, struct file_comp_lookup fcl[], + int num_files) +{ + struct ipc_comp_dev *icd; + struct processing_module *mod; + struct file_comp_data *fcd; + int i; + + for (i = 0; i < num_files; i++) { + if (!tb_is_pipeline_enabled(tp, fcl[i].pipeline_id)) { + fcl[i].id = -1; + continue; + } + + icd = ipc_get_comp_by_id(sof_get()->ipc, fcl[i].id); + if (!icd) { + fcl[i].state = NULL; + continue; + } + + if (!icd->cd) { + fprintf(stderr, "error: null cd.\n"); + return -EINVAL; + } + + mod = comp_mod(icd->cd); + if (!mod) { + fprintf(stderr, "error: null module.\n"); + return -EINVAL; + } + fcd = module_get_private_data(mod); + fcl[i].state = &fcd->fs; + } + + return 0; +} + +int tb_find_file_components(struct testbench_prm *tp) +{ + int ret; + + /* file read */ + ret = tb_find_file_helper(tp, tp->fr, tp->input_file_num); + if (ret < 0) + return ret; + + /* file write */ + ret = tb_find_file_helper(tp, tp->fw, tp->output_file_num); + return ret; +} + +static bool tb_is_file_component_at_eof(struct testbench_prm *tp) +{ + int i; + + for (i = 0; i < tp->input_file_num; i++) { + if (!tp->fr[i].state) + continue; + + if (tp->fr[i].state->reached_eof) + return true; + } + + for (i = 0; i < tp->output_file_num; i++) { + if (!tp->fw[i].state) + continue; + + if (tp->fw[i].state->reached_eof || tp->fw[i].state->write_failed) + return true; + } + + return false; +} + +void tb_show_file_stats(struct testbench_prm *tp, int pipeline_id) +{ + struct ipc_comp_dev *icd; + struct comp_dev *dev; + struct processing_module *mod; + struct file_comp_data *fcd; + int i; + + for (i = 0; i < tp->input_file_num; i++) { + if (tp->fr[i].id < 0 || tp->fr[i].pipeline_id != pipeline_id) + continue; + + icd = ipc_get_comp_by_id(sof_get()->ipc, tp->fr[i].id); + if (!icd) + continue; + + dev = icd->cd; + mod = comp_mod(dev); + fcd = module_get_private_data(mod); + printf("file %s: id %d: type %d: samples %d copies %d\n", + fcd->fs.fn, dev->ipc_config.id, dev->drv->type, fcd->fs.n, + fcd->fs.copy_count); + } + + for (i = 0; i < tp->output_file_num; i++) { + if (tp->fw[i].id < 0 || tp->fw[i].pipeline_id != pipeline_id) + continue; + + icd = ipc_get_comp_by_id(sof_get()->ipc, tp->fw[i].id); + if (!icd) + continue; + + dev = icd->cd; + mod = comp_mod(dev); + fcd = module_get_private_data(mod); + printf("file %s: id %d: type %d: samples %d copies %d\n", + fcd->fs.fn, dev->ipc_config.id, dev->drv->type, fcd->fs.n, + fcd->fs.copy_count); + } +} + +bool tb_is_pipeline_enabled(struct testbench_prm *tp, int pipeline_id) +{ + int i; + + for (i = 0; i < tp->pipeline_num; i++) { + if (tp->pipelines[i] == pipeline_id) + return true; + } + + return false; +} + +bool tb_schedule_pipeline_check_state(struct testbench_prm *tp) +{ + uint64_t cycles0, cycles1; + + tb_getcycles(&cycles0); + + schedule_ll_run_tasks(); + + tb_getcycles(&cycles1); + tp->total_cycles += cycles1 - cycles0; + + /* Check if all file components are running */ + return tb_is_file_component_at_eof(tp); +} + +struct ipc_data { + struct ipc_data_host_buffer dh_buffer; +}; + +void tb_free(struct sof *sof) +{ + struct schedule_data *sch; + struct schedulers **schedulers; + struct list_item *slist, *_slist; + struct notify **notify = arch_notify_get(); + struct ipc_data *iipc; + + free(*notify); + + /* free all scheduler data */ + schedule_free(0); + schedulers = arch_schedulers_get(); + list_for_item_safe(slist, _slist, &(*schedulers)->list) { + sch = container_of(slist, struct schedule_data, list); + list_item_del(slist); + free(sch); + } + free(*arch_schedulers_get()); + + /* free IPC data */ + iipc = sof->ipc->private; + free(sof->ipc->comp_data); + free(iipc->dh_buffer.page_table); + free(iipc); + free(sof->ipc); +} + +/* print debug messages */ +void tb_debug_print(char *message) +{ + if (host_trace_level >= LOG_LEVEL_DEBUG) + printf("debug: %s", message); +} + +/* enable trace in testbench */ +void tb_enable_trace(unsigned int log_level) +{ + host_trace_level = log_level; + if (host_trace_level) + tb_debug_print("trace print enabled\n"); + else + tb_debug_print("trace print disabled\n"); +} + +void tb_gettime(struct timespec *td) +{ +#if !defined __XCC__ + clock_gettime(CLOCK_MONOTONIC, td); +#else + td->tv_nsec = 0; + td->tv_sec = 0; +#endif +} + +void tb_getcycles(uint64_t *cycles) +{ +#if defined __XCC__ + *cycles = XT_RSR_CCOUNT(); +#else + *cycles = 0; +#endif +} diff --git a/tools/testbench/common_test_ipc3.c b/tools/testbench/common_test_ipc3.c index f43b1f36750f..ba3b77307b6e 100644 --- a/tools/testbench/common_test_ipc3.c +++ b/tools/testbench/common_test_ipc3.c @@ -2,39 +2,21 @@ // // Copyright(c) 2018-2024 Intel Corporation. All rights reserved. +#if CONFIG_IPC_MAJOR_3 + #include -#include -#include -#include -#include -#include -#include #include #include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include + #include #include #include -#include #include "testbench/common_test.h" -#include "testbench/trace.h" #include "testbench/file.h" -#if defined __XCC__ -#include -#endif - /* testbench helper functions for pipeline setup and trigger */ int tb_setup(struct sof *sof, struct testbench_prm *tp) @@ -88,42 +70,11 @@ int tb_setup(struct sof *sof, struct testbench_prm *tp) return -EINVAL; } - debug_print("ipc and scheduler initialized\n"); + tb_debug_print("ipc and scheduler initialized\n"); return 0; } -struct ipc_data { - struct ipc_data_host_buffer dh_buffer; -}; - -void tb_free(struct sof *sof) -{ - struct schedule_data *sch; - struct schedulers **schedulers; - struct list_item *slist, *_slist; - struct notify **notify = arch_notify_get(); - struct ipc_data *iipc; - - free(*notify); - - /* free all scheduler data */ - schedule_free(0); - schedulers = arch_schedulers_get(); - list_for_item_safe(slist, _slist, &(*schedulers)->list) { - sch = container_of(slist, struct schedule_data, list); - free(sch); - } - free(*arch_schedulers_get()); - - /* free IPC data */ - iipc = sof->ipc->private; - free(sof->ipc->comp_data); - free(iipc->dh_buffer.page_table); - free(iipc); - free(sof->ipc); -} - /* Get pipeline host component */ static struct comp_dev *tb_get_pipeline_host(struct pipeline *p) { @@ -199,8 +150,7 @@ int tb_pipeline_reset(struct ipc *ipc, struct pipeline *p) } /* pipeline pcm params */ -int tb_pipeline_params(struct testbench_prm *tp, struct ipc *ipc, struct pipeline *p, - struct tplg_context *ctx) +int tb_pipeline_params(struct testbench_prm *tp, struct ipc *ipc, struct pipeline *p) { struct comp_dev *cd; struct sof_ipc_pcm_params params = {{0}}; @@ -219,7 +169,7 @@ int tb_pipeline_params(struct testbench_prm *tp, struct ipc *ipc, struct pipelin /* Compute period from sample rates */ fs_period = (int)(0.9999 + tp->fs_in * period / 1e6); sprintf(message, "period sample count %d\n", fs_period); - debug_print(message); + tb_debug_print(message); /* set pcm params */ params.comp_id = p->comp_id; @@ -267,38 +217,211 @@ int tb_pipeline_params(struct testbench_prm *tp, struct ipc *ipc, struct pipelin return ret; } -/* print debug messages */ -void debug_print(char *message) +int tb_set_running_state(struct testbench_prm *tp) +{ + return 0; +} + +static struct pipeline *tb_get_pipeline_by_id(int id) +{ + struct ipc_comp_dev *pcm_dev; + struct ipc *ipc = sof_get()->ipc; + + pcm_dev = ipc_get_ppl_src_comp(ipc, id); + return pcm_dev->cd->pipeline; +} + +int tb_set_reset_state(struct testbench_prm *tp) +{ + struct pipeline *p; + struct ipc *ipc = sof_get()->ipc; + int ret = 0; + int i; + + for (i = 0; i < tp->pipeline_num; i++) { + p = tb_get_pipeline_by_id(tp->pipelines[i]); + ret = tb_pipeline_reset(ipc, p); + if (ret < 0) + break; + } + + return ret; +} + +static void test_pipeline_free_comps(int pipeline_id) +{ + struct list_item *clist; + struct list_item *temp; + struct ipc_comp_dev *icd = NULL; + int err; + + /* remove the components for this pipeline */ + list_for_item_safe(clist, temp, &sof_get()->ipc->comp_list) { + icd = container_of(clist, struct ipc_comp_dev, list); + + switch (icd->type) { + case COMP_TYPE_COMPONENT: + if (icd->cd->pipeline->pipeline_id != pipeline_id) + break; + err = ipc_comp_free(sof_get()->ipc, icd->id); + if (err) + fprintf(stderr, "failed to free comp %d\n", icd->id); + break; + case COMP_TYPE_BUFFER: + if (buffer_pipeline_id(icd->cb) != pipeline_id) + break; + err = ipc_buffer_free(sof_get()->ipc, icd->id); + if (err) + fprintf(stderr, "failed to free buffer %d\n", icd->id); + break; + case COMP_TYPE_PIPELINE: + if (icd->pipeline->pipeline_id != pipeline_id) + break; + err = ipc_pipeline_free(sof_get()->ipc, icd->id); + if (err) + fprintf(stderr, "failed to free pipeline %d\n", icd->id); + break; + default: + fprintf(stderr, "Unknown icd->type %d\n", icd->type); + } + } +} + +int tb_free_all_pipelines(struct testbench_prm *tp) +{ + int i; + + for (i = 0; i < tp->pipeline_num; i++) + test_pipeline_free_comps(tp->pipelines[i]); + + return 0; +} + +void tb_free_topology(struct testbench_prm *tp) { - if (host_trace_level >= LOG_LEVEL_DEBUG) - printf("debug: %s", message); } -/* enable trace in testbench */ -void tb_enable_trace(unsigned int log_level) +static int test_pipeline_params(struct testbench_prm *tp) { - host_trace_level = log_level; - if (host_trace_level) - debug_print("trace print enabled\n"); - else - debug_print("trace print disabled\n"); + struct ipc_comp_dev *pcm_dev; + struct pipeline *p; + struct ipc *ipc = sof_get()->ipc; + int ret = 0; + int i; + + /* Run pipeline until EOF from fileread */ + + for (i = 0; i < tp->pipeline_num; i++) { + pcm_dev = ipc_get_ppl_src_comp(ipc, tp->pipelines[i]); + if (!pcm_dev) { + fprintf(stderr, "error: pipeline %d has no source component\n", + tp->pipelines[i]); + return -EINVAL; + } + + /* set up pipeline params */ + p = pcm_dev->cd->pipeline; + + /* input and output sample rate */ + if (!tp->fs_in) + tp->fs_in = p->period * p->frames_per_sched; + + if (!tp->fs_out) + tp->fs_out = p->period * p->frames_per_sched; + + ret = tb_pipeline_params(tp, ipc, p); + if (ret < 0) { + fprintf(stderr, "error: pipeline params failed: %s\n", + strerror(ret)); + return ret; + } + } + + return 0; } -void tb_gettime(struct timespec *td) +static void tb_test_pipeline_set_test_limits(int pipeline_id, int max_copies, + int max_samples) { -#if !defined __XCC__ - clock_gettime(CLOCK_MONOTONIC, td); -#else - td->tv_nsec = 0; - td->tv_sec = 0; -#endif + struct list_item *clist; + struct ipc_comp_dev *icd = NULL; + struct comp_dev *cd; + struct dai_data *dd; + struct file_comp_data *fcd; + + /* set the test limits for this pipeline */ + list_for_item(clist, &sof_get()->ipc->comp_list) { + icd = container_of(clist, struct ipc_comp_dev, list); + + switch (icd->type) { + case COMP_TYPE_COMPONENT: + cd = icd->cd; + if (cd->pipeline->pipeline_id != pipeline_id) + break; + + switch (cd->drv->type) { + case SOF_COMP_HOST: + case SOF_COMP_DAI: + case SOF_COMP_FILEREAD: + case SOF_COMP_FILEWRITE: + /* only file limits supported today. TODO: add others */ + dd = comp_get_drvdata(cd); + fcd = comp_get_drvdata(dd->dai); + fcd->max_samples = max_samples; + fcd->max_copies = max_copies; + break; + default: + break; + } + break; + case COMP_TYPE_BUFFER: + default: + break; + } + } } -void tb_getcycles(uint64_t *cycles) +static int test_pipeline_start(struct testbench_prm *tp) { -#if defined __XCC__ - *cycles = XT_RSR_CCOUNT(); -#else - *cycles = 0; -#endif + struct pipeline *p; + struct ipc *ipc = sof_get()->ipc; + int i; + + /* Run pipeline until EOF from fileread */ + for (i = 0; i < tp->pipeline_num; i++) { + p = tb_get_pipeline_by_id(tp->pipelines[i]); + + /* do we need to apply copy count limit ? */ + if (tp->copy_check) + tb_test_pipeline_set_test_limits(tp->pipelines[i], tp->copy_iterations, 0); + + /* set pipeline params and trigger start */ + if (tb_pipeline_start(ipc, p) < 0) { + fprintf(stderr, "error: pipeline params\n"); + return -EINVAL; + } + } + + return 0; } + +int tb_set_up_all_pipelines(struct testbench_prm *tp) +{ + int ret; + + ret = test_pipeline_params(tp); + if (ret < 0) { + fprintf(stderr, "error: pipeline params failed %d\n", ret); + return ret; + } + + ret = test_pipeline_start(tp); + if (ret < 0) { + fprintf(stderr, "error: pipeline failed %d\n", ret); + return ret; + } + + return 0; +} + +#endif /* CONFIG_IPC_MAJOR_3 */ diff --git a/tools/testbench/file.c b/tools/testbench/file.c index bd3584155005..d46d691091f5 100644 --- a/tools/testbench/file.c +++ b/tools/testbench/file.c @@ -557,7 +557,7 @@ static int file_init(struct processing_module *mod) struct file_comp_data *cd; int ret; - debug_print("file_init()\n"); + tb_debug_print("file_init()\n"); cd = rzalloc(SOF_MEM_ZONE_RUNTIME_SHARED, 0, SOF_MEM_CAPS_RAM, sizeof(*cd)); if (!cd) @@ -647,7 +647,7 @@ static int file_free(struct processing_module *mod) { struct file_comp_data *cd = module_get_private_data(mod); - debug_print("file_free()"); + tb_debug_print("file_free()"); if (cd->fs.mode == FILE_READ) fclose(cd->fs.rfh); @@ -711,7 +711,7 @@ static int file_process(struct processing_module *mod, cd->fs.copy_count++; if (cd->fs.reached_eof || (cd->max_copies && cd->fs.copy_count >= cd->max_copies)) { cd->fs.reached_eof = 1; - debug_print("file_process(): reached EOF"); + tb_debug_print("file_process(): reached EOF"); schedule_task_cancel(mod->dev->pipeline->pipe_task); } @@ -729,7 +729,7 @@ static int file_prepare(struct processing_module *mod, struct comp_dev *dev = mod->dev; struct file_comp_data *cd = module_get_private_data(mod); - debug_print("file_prepare()"); + tb_debug_print("file_prepare()"); /* file component sink/source buffer period count */ cd->max_frames = dev->frames; @@ -769,14 +769,14 @@ static int file_prepare(struct processing_module *mod, static int file_reset(struct processing_module *mod) { - debug_print("file_reset()"); + tb_debug_print("file_reset()"); return 0; } static int file_trigger(struct comp_dev *dev, int cmd) { - debug_print("asrc_trigger()"); + tb_debug_print("asrc_trigger()"); return comp_set_state(dev, cmd); } @@ -787,7 +787,7 @@ static int file_get_hw_params(struct comp_dev *dev, struct processing_module *mod = comp_mod(dev); struct file_comp_data *cd = module_get_private_data(mod); - debug_print("file_hw_params()"); + tb_debug_print("file_hw_params()"); params->direction = dir; params->rate = cd->rate; params->channels = cd->channels; diff --git a/tools/testbench/include/testbench/common_test.h b/tools/testbench/include/testbench/common_test.h index 40e7b38b1216..7245eacba367 100644 --- a/tools/testbench/include/testbench/common_test.h +++ b/tools/testbench/include/testbench/common_test.h @@ -6,15 +6,10 @@ #ifndef _TESTBENCH_COMMON_TEST_H #define _TESTBENCH_COMMON_TEST_H +#include #include -#include #include -#include -#include -#include -#include -#include -#include + #include @@ -23,12 +18,20 @@ #define TB_MAX_INPUT_FILE_NUM 16 #define TB_MAX_OUTPUT_FILE_NUM 16 +#define TB_MAX_PIPELINES_NUM 16 /* number of widgets types supported in testbench */ #define TB_NUM_WIDGETS_SUPPORTED 16 struct tplg_context; +struct file_comp_lookup { + int id; + int instance_id; + int pipeline_id; + struct file_state *state; +}; + /* * Global testbench data. * @@ -37,29 +40,22 @@ struct tplg_context; */ struct testbench_prm { long long total_cycles; - char *tplg_file; /* topology file to use */ + int pipelines[TB_MAX_PIPELINES_NUM]; + struct file_comp_lookup fr[TB_MAX_INPUT_FILE_NUM]; + struct file_comp_lookup fw[TB_MAX_OUTPUT_FILE_NUM]; char *input_file[TB_MAX_INPUT_FILE_NUM]; /* input file names */ char *output_file[TB_MAX_OUTPUT_FILE_NUM]; /* output file names */ + char *tplg_file; /* topology file to use */ + char *bits_in; /* input bit format */ int input_file_num; /* number of input files */ int output_file_num; /* number of output files */ - char *bits_in; /* input bit format */ - int pipelines[TB_MAX_OUTPUT_FILE_NUM]; /* output file names */ int pipeline_num; - struct tplg_context *ctx; - - int fr_id; - int fw_id; - - int max_pipeline_id; int copy_iterations; bool copy_check; bool quiet; int dynamic_pipeline_iterations; - int num_vcores; int tick_period_us; int pipeline_duration_ms; - int real_time; - FILE *file; char *pipeline_string; int output_file_index; int input_file_index; @@ -79,30 +75,32 @@ struct testbench_prm { uint32_t channels_in; uint32_t channels_out; enum sof_ipc_frame frame_fmt; + + /* topology */ + struct tplg_context tplg; }; extern int debug; -int tb_parse_topology(struct testbench_prm *tb, struct tplg_context *ctx); - -int edf_scheduler_init(void); - -int tb_setup(struct sof *sof, struct testbench_prm *tp); -void tb_free(struct sof *sof); - +int tb_find_file_components(struct testbench_prm *tp); +int tb_free_all_pipelines(struct testbench_prm *tp); +int tb_load_topology(struct testbench_prm *tp); +int tb_parse_topology(struct testbench_prm *tp); +int tb_pipeline_params(struct testbench_prm *tp, struct ipc *ipc, struct pipeline *p); +int tb_pipeline_reset(struct ipc *ipc, struct pipeline *p); int tb_pipeline_start(struct ipc *ipc, struct pipeline *p); - -int tb_pipeline_params(struct testbench_prm *tp, struct ipc *ipc, struct pipeline *p, - struct tplg_context *ctx); - int tb_pipeline_stop(struct ipc *ipc, struct pipeline *p); - -int tb_pipeline_reset(struct ipc *ipc, struct pipeline *p); - -void debug_print(char *message); - -void tb_gettime(struct timespec *td); - +int tb_set_reset_state(struct testbench_prm *tp); +int tb_set_running_state(struct testbench_prm *tp); +int tb_set_up_all_pipelines(struct testbench_prm *tp); +int tb_setup(struct sof *sof, struct testbench_prm *tp); +bool tb_is_pipeline_enabled(struct testbench_prm *tp, int pipeline_id); +bool tb_schedule_pipeline_check_state(struct testbench_prm *tp); +void tb_debug_print(char *message); +void tb_free(struct sof *sof); +void tb_free_topology(struct testbench_prm *tp); void tb_getcycles(uint64_t *cycles); +void tb_gettime(struct timespec *td); +void tb_show_file_stats(struct testbench_prm *tp, int pipeline_id); #endif /* _TESTBENCH_COMMON_TEST_H */ diff --git a/tools/testbench/testbench.c b/tools/testbench/testbench.c index 60f80853483c..16d2fa771c83 100644 --- a/tools/testbench/testbench.c +++ b/tools/testbench/testbench.c @@ -8,16 +8,18 @@ #include #include #include -#include #include -#include -#include "testbench/common_test.h" #include + #include "testbench/trace.h" #include "testbench/file.h" +#include "testbench/common_test.h" + +#include #include -#include #include +#include +#include #define TESTBENCH_NCH 2 @@ -142,140 +144,12 @@ static void print_usage(char *executable) printf("-b S16_LE -a volume=libsof_volume.so\n"); } -/* free components */ -static void test_pipeline_free_comps(int pipeline_id) -{ - struct list_item *clist; - struct list_item *temp; - struct ipc_comp_dev *icd = NULL; - int err; - - /* remove the components for this pipeline */ - list_for_item_safe(clist, temp, &sof_get()->ipc->comp_list) { - icd = container_of(clist, struct ipc_comp_dev, list); - - switch (icd->type) { - case COMP_TYPE_COMPONENT: - if (icd->cd->pipeline->pipeline_id != pipeline_id) - break; - err = ipc_comp_free(sof_get()->ipc, icd->id); - if (err) - fprintf(stderr, "failed to free comp %d\n", - icd->id); - break; - case COMP_TYPE_BUFFER: - if (buffer_pipeline_id(icd->cb) != pipeline_id) - break; - err = ipc_buffer_free(sof_get()->ipc, icd->id); - if (err) - fprintf(stderr, "failed to free buffer %d\n", - icd->id); - break; - default: - if (icd->pipeline->pipeline_id != pipeline_id) - break; - err = ipc_pipeline_free(sof_get()->ipc, icd->id); - if (err) - fprintf(stderr, "failed to free pipeline %d\n", - icd->id); - break; - } - } -} - -static void test_pipeline_set_test_limits(int pipeline_id, int max_copies, - int max_samples) -{ - struct list_item *clist; - struct list_item *temp; - struct ipc_comp_dev *icd = NULL; - struct comp_dev *cd; - struct dai_data *dd; - struct file_comp_data *fcd; - - /* set the test limits for this pipeline */ - list_for_item_safe(clist, temp, &sof_get()->ipc->comp_list) { - icd = container_of(clist, struct ipc_comp_dev, list); - - switch (icd->type) { - case COMP_TYPE_COMPONENT: - cd = icd->cd; - if (cd->pipeline->pipeline_id != pipeline_id) - break; - - switch (cd->drv->type) { - case SOF_COMP_HOST: - case SOF_COMP_DAI: - case SOF_COMP_FILEREAD: - case SOF_COMP_FILEWRITE: - /* only file limits supported today. TODO: add others */ - dd = comp_get_drvdata(cd); - fcd = comp_get_drvdata(dd->dai); - fcd->max_samples = max_samples; - fcd->max_copies = max_copies; - break; - default: - break; - } - break; - case COMP_TYPE_BUFFER: - default: - break; - } - } -} - -static void test_pipeline_get_file_stats(int pipeline_id) -{ - struct list_item *clist; - struct list_item *temp; - struct ipc_comp_dev *icd; - struct comp_dev *cd; - struct dai_data *dd; - struct file_comp_data *fcd; - unsigned long time; - - /* get the file IO status for each file in pipeline */ - list_for_item_safe(clist, temp, &sof_get()->ipc->comp_list) { - icd = container_of(clist, struct ipc_comp_dev, list); - - switch (icd->type) { - case COMP_TYPE_COMPONENT: - cd = icd->cd; - if (cd->pipeline->pipeline_id != pipeline_id) - break; - switch (cd->drv->type) { - case SOF_COMP_HOST: - case SOF_COMP_DAI: - case SOF_COMP_FILEREAD: - case SOF_COMP_FILEWRITE: - dd = comp_get_drvdata(cd); - fcd = comp_get_drvdata(dd->dai); - - time = cd->pipeline->pipe_task->start; - if (fcd->fs.copy_count == 0) - fcd->fs.copy_count = 1; - printf("file %s: id %d: type %d: samples %d copies %d total time %lu uS avg time %lu uS\n", - fcd->fs.fn, cd->ipc_config.id, cd->drv->type, fcd->fs.n, - fcd->fs.copy_count, time, time / fcd->fs.copy_count); - break; - default: - break; - } - break; - case COMP_TYPE_BUFFER: - default: - break; - } - } -} - static int parse_input_args(int argc, char **argv, struct testbench_prm *tp) { int option = 0; int ret = 0; - while ((option = getopt(argc, argv, "hdqi:o:t:b:a:r:R:c:n:C:P:Vp:T:D:")) != -1) { + while ((option = getopt(argc, argv, "hd:qi:o:t:b:r:R:c:n:C:P:p:T:D:")) != -1) { switch (option) { /* input sample file */ case 'i': @@ -371,232 +245,47 @@ static int parse_input_args(int argc, char **argv, struct testbench_prm *tp) return ret; } -static struct pipeline *get_pipeline_by_id(int id) +static void test_pipeline_stats(struct testbench_prm *tp, long long delta_t) { - struct ipc_comp_dev *pcm_dev; - struct ipc *ipc = sof_get()->ipc; - - pcm_dev = ipc_get_ppl_src_comp(ipc, id); - return pcm_dev->cd->pipeline; -} - -static int test_pipeline_stop(struct testbench_prm *tp) -{ - struct pipeline *p; - struct ipc *ipc = sof_get()->ipc; - int ret = 0; - int i; - - for (i = 0; i < tp->pipeline_num; i++) { - p = get_pipeline_by_id(tp->pipelines[i]); - ret = tb_pipeline_stop(ipc, p); - if (ret < 0) - break; - } - - return ret; -} - -static int test_pipeline_reset(struct testbench_prm *tp) -{ - struct pipeline *p; - struct ipc *ipc = sof_get()->ipc; - int ret = 0; - int i; - - for (i = 0; i < tp->pipeline_num; i++) { - p = get_pipeline_by_id(tp->pipelines[i]); - ret = tb_pipeline_reset(ipc, p); - if (ret < 0) - break; - } - - return ret; -} - -static void test_pipeline_free(struct testbench_prm *tp) -{ - int i; - - for (i = 0; i < tp->pipeline_num; i++) - test_pipeline_free_comps(tp->pipelines[i]); -} - -static int test_pipeline_params(struct testbench_prm *tp, struct tplg_context *ctx) -{ - struct ipc_comp_dev *pcm_dev; - struct pipeline *p; - struct ipc *ipc = sof_get()->ipc; - int ret = 0; - int i; - - /* Run pipeline until EOF from fileread */ - - for (i = 0; i < tp->pipeline_num; i++) { - pcm_dev = ipc_get_ppl_src_comp(ipc, tp->pipelines[i]); - if (!pcm_dev) { - fprintf(stderr, "error: pipeline %d has no source component\n", - tp->pipelines[i]); - return -EINVAL; - } - - /* set up pipeline params */ - p = pcm_dev->cd->pipeline; - - /* input and output sample rate */ - if (!tp->fs_in) - tp->fs_in = p->period * p->frames_per_sched; - - if (!tp->fs_out) - tp->fs_out = p->period * p->frames_per_sched; - - ret = tb_pipeline_params(tp, ipc, p, ctx); - if (ret < 0) { - fprintf(stderr, "error: pipeline params failed: %s\n", - strerror(ret)); - return ret; - } - } - - - return 0; -} - -static int test_pipeline_start(struct testbench_prm *tp) -{ - struct pipeline *p; - struct ipc *ipc = sof_get()->ipc; - int i; - - /* Run pipeline until EOF from fileread */ - for (i = 0; i < tp->pipeline_num; i++) { - p = get_pipeline_by_id(tp->pipelines[i]); - - /* do we need to apply copy count limit ? */ - if (tp->copy_check) - test_pipeline_set_test_limits(tp->pipelines[i], tp->copy_iterations, 0); - - /* set pipeline params and trigger start */ - if (tb_pipeline_start(ipc, p) < 0) { - fprintf(stderr, "error: pipeline params\n"); - return -EINVAL; - } - } - - return 0; -} - -static bool test_pipeline_check_state(struct testbench_prm *tp, int state) -{ - struct pipeline *p; - uint64_t cycles0, cycles1; - int i; - - tb_getcycles(&cycles0); - - schedule_ll_run_tasks(); - - tb_getcycles(&cycles1); - tp->total_cycles += cycles1 - cycles0; - - /* Run pipeline until EOF from fileread */ - for (i = 0; i < tp->pipeline_num; i++) { - p = get_pipeline_by_id(tp->pipelines[i]); - if (p->pipe_task->state == state) - return true; - } - - return false; -} - -static int test_pipeline_load(struct testbench_prm *tp, struct tplg_context *ctx) -{ - int ret; - - /* setup the thread virtual core config */ - memset(ctx, 0, sizeof(*ctx)); - ctx->comp_id = 1; - ctx->core_id = 0; - ctx->sof = sof_get(); - ctx->tplg_file = tp->tplg_file; - ctx->ipc_major = 3; - - /* parse topology file and create pipeline */ - ret = tb_parse_topology(tp, ctx); - if (ret < 0) - fprintf(stderr, "error: parsing topology\n"); - - return ret; -} - -static void test_pipeline_stats(struct testbench_prm *tp, - struct tplg_context *ctx, long long delta_t) -{ - int count = 1; - struct ipc_comp_dev *icd; - struct comp_dev *file_dev; - struct processing_module *file_mod; - struct pipeline *p; - struct file_comp_data *frcd, *fwcd; long long file_cycles, pipeline_cycles; float pipeline_mcps; int n_in, n_out, frames_out; int i; + int count = 1; - /* Get pointer to filewrite */ - icd = ipc_get_comp_by_id(sof_get()->ipc, tp->fw_id); - if (!icd) { - fprintf(stderr, "error: failed to get pointers to filewrite\n"); - exit(EXIT_FAILURE); - } - file_dev = icd->cd; - file_mod = comp_mod(file_dev); - fwcd = module_get_private_data(file_mod); - - /* Get pointer to fileread */ - icd = ipc_get_comp_by_id(sof_get()->ipc, tp->fr_id); - if (!icd) { - fprintf(stderr, "error: failed to get pointers to fileread\n"); - exit(EXIT_FAILURE); - } - file_dev = icd->cd; - file_mod = comp_mod(file_dev); - frcd = module_get_private_data(file_mod); - - /* Run pipeline until EOF from fileread */ - icd = ipc_get_comp_by_id(sof_get()->ipc, ctx->sched_id); - p = icd->cd->pipeline; + n_out = 0; + n_in = 0; + file_cycles = 0; + for (i = 0; i < tp->input_file_num; i++) { + if (tp->fr[i].id < 0) + continue; - /* input and output sample rate */ - if (!tp->fs_in) - tp->fs_in = p->period * p->frames_per_sched; + n_in += tp->fr[i].state->n; + file_cycles += tp->fr[i].state->cycles_count; + } - if (!tp->fs_out) - tp->fs_out = p->period * p->frames_per_sched; + for (i = 0; i < tp->output_file_num; i++) { + if (tp->fw[i].id < 0) + continue; - n_in = frcd->fs.n; - n_out = fwcd->fs.n; - file_cycles = frcd->fs.cycles_count + fwcd->fs.cycles_count; + n_out += tp->fw[i].state->n; + file_cycles += tp->fw[i].state->cycles_count; + } /* print test summary */ printf("==========================================================\n"); printf(" Test Summary %d\n", count); printf("==========================================================\n"); - printf("Test Pipeline:\n"); - printf("%s\n", tp->pipeline_string); - test_pipeline_get_file_stats(ctx->pipeline_id); + + for (i = 0; i < tp->pipeline_num; i++) { + printf("pipeline %d\n", tp->pipelines[i]); + tb_show_file_stats(tp, tp->pipelines[i]); + } printf("Input bit format: %s\n", tp->bits_in); printf("Input sample rate: %d\n", tp->fs_in); printf("Output sample rate: %d\n", tp->fs_out); - for (i = 0; i < tp->input_file_num; i++) { - printf("Input[%d] read from file: \"%s\"\n", - i, tp->input_file[i]); - } - for (i = 0; i < tp->output_file_num; i++) { - printf("Output[%d] written to file: \"%s\"\n", - i, tp->output_file[i]); - } + frames_out = n_out / tp->channels_out; printf("Input sample (frame) count: %d (%d)\n", n_in, n_in / tp->channels_in); printf("Output sample (frame) count: %d (%d)\n", n_out, frames_out); @@ -625,7 +314,6 @@ static void test_pipeline_stats(struct testbench_prm *tp, static int pipline_test(struct testbench_prm *tp) { int dp_count = 0; - struct tplg_context ctx; struct timespec ts; struct timespec td0, td1; long long delta_t; @@ -643,24 +331,27 @@ static int pipline_test(struct testbench_prm *tp) printf(" Test Start %d\n", dp_count); printf("==========================================================\n"); - err = test_pipeline_load(tp, &ctx); + err = tb_load_topology(tp); if (err < 0) { - fprintf(stderr, "error: pipeline load %d failed %d\n", - dp_count, err); + fprintf(stderr, "error: topology load %d failed %d\n", dp_count, err); break; } - err = test_pipeline_params(tp, &ctx); + err = tb_set_up_all_pipelines(tp); if (err < 0) { - fprintf(stderr, "error: pipeline params %d failed %d\n", - dp_count, err); + fprintf(stderr, "error: pipelines set up %d failed %d\n", dp_count, err); break; } - err = test_pipeline_start(tp); + err = tb_set_running_state(tp); if (err < 0) { - fprintf(stderr, "error: pipeline run %d failed %d\n", - dp_count, err); + fprintf(stderr, "error: pipelines state set %d failed %d\n", dp_count, err); + break; + } + + err = tb_find_file_components(tp); /* Track file comp status during copying */ + if (err < 0) { + fprintf(stderr, "error: file component find failed %d\n", err); break; } @@ -687,10 +378,8 @@ static int pipline_test(struct testbench_prm *tp) #endif if (err == 0) { nsleep_time += tp->tick_period_us; /* sleep fully completed */ - if (test_pipeline_check_state(tp, SOF_TASK_STATE_CANCEL)) { - fprintf(stdout, "pipeline cancelled !\n"); + if (tb_schedule_pipeline_check_state(tp)) break; - } } else { if (err == EINTR) { continue; /* interrupted - keep going */ @@ -701,129 +390,124 @@ static int pipline_test(struct testbench_prm *tp) } } + tb_schedule_pipeline_check_state(tp); /* Once more to flush out remaining data */ + tb_gettime(&td1); - err = test_pipeline_stop(tp); + err = tb_set_reset_state(tp); if (err < 0) { - fprintf(stderr, "error: pipeline stop %d failed %d\n", + fprintf(stderr, "error: pipeline reset %d failed %d\n", dp_count, err); break; } + /* TODO: This should be printed after reset and free to get cleaner output + * but the file internal status would be lost there. + */ delta_t = (td1.tv_sec - td0.tv_sec) * 1000000; delta_t += (td1.tv_nsec - td0.tv_nsec) / 1000; - test_pipeline_stats(tp, &ctx, delta_t); + test_pipeline_stats(tp, delta_t); - err = test_pipeline_reset(tp); + err = tb_free_all_pipelines(tp); if (err < 0) { - fprintf(stderr, "error: pipeline stop %d failed %d\n", - dp_count, err); + fprintf(stderr, "error: free pipelines %d failed %d\n", dp_count, err); break; } - test_pipeline_free(tp); - + tb_free_topology(tp); dp_count++; } return 0; } -static struct testbench_prm tp; - int main(int argc, char **argv) { - int i, err; + struct testbench_prm *tp; + int i, ret; + + tp = calloc(1, sizeof(struct testbench_prm)); + if (!tp) + return EXIT_FAILURE; /* initialize input and output sample rates, files, etc. */ - tp.total_cycles = 0; - tp.fs_in = 0; - tp.fs_out = 0; - tp.bits_in = 0; - tp.tplg_file = NULL; - tp.input_file_num = 0; - tp.output_file_num = 0; - for (i = 0; i < TB_MAX_OUTPUT_FILE_NUM; i++) - tp.output_file[i] = NULL; - - for (i = 0; i < TB_MAX_INPUT_FILE_NUM; i++) - tp.input_file[i] = NULL; - - tp.channels_in = TESTBENCH_NCH; - tp.channels_out = 0; - tp.max_pipeline_id = 0; - tp.copy_check = false; - tp.quiet = 0; - tp.dynamic_pipeline_iterations = 1; - tp.pipeline_string = calloc(1, TB_DEBUG_MSG_LEN); - tp.pipelines[0] = 1; - tp.pipeline_num = 1; - tp.tick_period_us = 0; /* Execute fast non-real time, for 1 ms tick use -T 1000 */ - tp.pipeline_duration_ms = 5000; - tp.copy_iterations = 1; + tp->channels_in = TESTBENCH_NCH; + tp->copy_check = false; + tp->dynamic_pipeline_iterations = 1; + tp->pipeline_string = calloc(1, TB_DEBUG_MSG_LEN); + tp->pipelines[0] = 1; + tp->pipeline_num = 1; + tp->pipeline_duration_ms = 5000; + tp->copy_iterations = 1; /* command line arguments*/ - err = parse_input_args(argc, argv, &tp); - if (err < 0) + ret = parse_input_args(argc, argv, tp); + if (ret < 0) goto out; - if (!tp.channels_out) - tp.channels_out = tp.channels_in; + if (!tp->channels_out) + tp->channels_out = tp->channels_in; /* check mandatory args */ - if (!tp.tplg_file) { + if (!tp->tplg_file) { fprintf(stderr, "topology file not specified, use -t file.tplg\n"); print_usage(argv[0]); - exit(EXIT_FAILURE); + ret = EXIT_FAILURE; + goto out; } - if (!tp.input_file_num) { + if (!tp->input_file_num) { fprintf(stderr, "input files not specified, use -i file1,file2\n"); print_usage(argv[0]); - exit(EXIT_FAILURE); + ret = EXIT_FAILURE; + goto out; } - if (!tp.output_file_num) { + if (!tp->output_file_num) { fprintf(stderr, "output files not specified, use -o file1,file2\n"); print_usage(argv[0]); - exit(EXIT_FAILURE); + ret = EXIT_FAILURE; + goto out; } - if (!tp.bits_in) { + if (!tp->bits_in) { fprintf(stderr, "input format not specified, use -b format\n"); print_usage(argv[0]); - exit(EXIT_FAILURE); + ret = EXIT_FAILURE; + goto out; } - if (tp.quiet) + if (tp->quiet) tb_enable_trace(0); /* reduce trace output */ else tb_enable_trace(1); /* initialize ipc and scheduler */ - if (tb_setup(sof_get(), &tp) < 0) { + if (tb_setup(sof_get(), tp) < 0) { fprintf(stderr, "error: pipeline init\n"); - exit(EXIT_FAILURE); + ret = EXIT_FAILURE; + goto out; } /* build, run and teardown pipelines */ - pipline_test(&tp); + pipline_test(tp); /* free other core FW services */ tb_free(sof_get()); + ret = EXIT_SUCCESS; out: /* free all other data */ - free(tp.bits_in); - free(tp.tplg_file); - for (i = 0; i < tp.output_file_num; i++) - free(tp.output_file[i]); + free(tp->bits_in); + free(tp->tplg_file); + for (i = 0; i < tp->output_file_num; i++) + free(tp->output_file[i]); - for (i = 0; i < tp.input_file_num; i++) - free(tp.input_file[i]); + for (i = 0; i < tp->input_file_num; i++) + free(tp->input_file[i]); - free(tp.pipeline_string); - - return EXIT_SUCCESS; + free(tp->pipeline_string); + free(tp); + return ret; } diff --git a/tools/testbench/topology_ipc3.c b/tools/testbench/topology_ipc3.c index d6163e19d6f9..b61336292e14 100644 --- a/tools/testbench/topology_ipc3.c +++ b/tools/testbench/topology_ipc3.c @@ -7,6 +7,8 @@ /* Topology loader to set up components and pipeline */ +#if CONFIG_IPC_MAJOR_3 + #include #include #include @@ -15,12 +17,14 @@ #include #include #include + +#include "testbench/common_test.h" +#include "testbench/file.h" + #include #include #include #include -#include "testbench/common_test.h" -#include "testbench/file.h" #define MAX_TPLG_OBJECT_SIZE 4096 @@ -376,12 +380,13 @@ static int tb_register_fileread(struct testbench_prm *tp, /* configure fileread */ fileread->fn = strdup(tp->input_file[tp->input_file_index]); - if (tp->input_file_index == 0) - tp->fr_id = ctx->comp_id; + tp->fr[tp->input_file_index].id = ctx->comp_id; + tp->fr[tp->input_file_index].instance_id = ctx->comp_id; + tp->fr[tp->input_file_index].pipeline_id = ctx->pipeline_id; + tp->input_file_index++; /* use fileread comp as scheduling comp */ ctx->sched_id = ctx->comp_id; - tp->input_file_index++; /* Set format from testbench command line*/ fileread->rate = tp->fs_in; @@ -428,8 +433,9 @@ static int tb_register_filewrite(struct testbench_prm *tp, return -EINVAL; } filewrite->fn = strdup(tp->output_file[tp->output_file_index]); - if (tp->output_file_index == 0) - tp->fw_id = ctx->comp_id; + tp->fw[tp->output_file_index].id = ctx->comp_id; + tp->fw[tp->output_file_index].instance_id = ctx->comp_id; + tp->fw[tp->output_file_index].pipeline_id = ctx->pipeline_id; tp->output_file_index++; /* Set format from testbench command line*/ @@ -626,9 +632,10 @@ static int tb_load_widget(struct testbench_prm *tb, struct tplg_context *ctx) } /* parse topology file and set up pipeline */ -int tb_parse_topology(struct testbench_prm *tb, struct tplg_context *ctx) +int tb_parse_topology(struct testbench_prm *tb) { + struct tplg_context *ctx = &tb->tplg; struct snd_soc_tplg_hdr *hdr; struct tplg_comp_info *comp_list_realloc = NULL; char pipeline_string[256] = {0}; @@ -637,6 +644,8 @@ int tb_parse_topology(struct testbench_prm *tb, struct tplg_context *ctx) FILE *file; size_t size; + ctx->ipc_major = 3; + /* open topology file */ file = fopen(ctx->tplg_file, "rb"); if (!file) { @@ -752,3 +761,4 @@ int tb_parse_topology(struct testbench_prm *tb, struct tplg_context *ctx) return ret; } +#endif /* CONFIG_IPC_MAJOR_3 */ From 8db545feafd64ff9b79d7ea3061b1882082b66ae Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Tue, 24 Sep 2024 12:36:22 +0300 Subject: [PATCH 15/18] Tools: Testbench: Error if filename is empty in file component This change avoids a segfault. Topologies may contain non-supported PCMs such as HDMI and if pipelines are not restricted with -p A,B,C,.. option file might get set up without filename. Signed-off-by: Seppo Ingalsuo --- tools/testbench/file.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tools/testbench/file.c b/tools/testbench/file.c index d46d691091f5..a4f87b855ac4 100644 --- a/tools/testbench/file.c +++ b/tools/testbench/file.c @@ -569,7 +569,12 @@ static int file_init(struct processing_module *mod) cd->file_func = file_default; /* get filename from IPC and open file */ - cd->fs.fn = strdup(ipc_file->fn); + if (ipc_file->fn) { + cd->fs.fn = strdup(ipc_file->fn); + } else { + fprintf(stderr, "error: no filename set\n"); + goto error; + } /* set file format */ cd->fs.f_format = get_file_format(cd->fs.fn); From 5b3f6ecc0b8f5573c5fc77cef057b13224d26d7f Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Tue, 17 Sep 2024 19:59:51 +0300 Subject: [PATCH 16/18] Tools: Testbench: Add IPC4 support This patch adds topology parsing and common functions versions for IPC4. Due to dai_get_init_delay_ms() implementation in IPC4 build the file component is changed internally to copier to provide the DAI data struct. The change is common for both IPC3 and IPC4 though copier is not usually used with IPC3 systems. Since it works the same solution is used. The file state retrieve is changed because the file component data is placed deeper into the structures. Due to IPC4 scheduling of pipelines the file component is added a timeout. A file component sets timeout status if there has been three copy operations with no data to process. The timeout and EOF are used to end cleanly the test run. The library_defconfig still has CONFIG_IPC_MAJOR_4=n. The add of build type select to scripts/rebuild-testbench.sh is further work. Also the IPC4 testbench in this state is not well usable with only one component supported as process component and without byte control set up algorithms. Test run with DC blocker is possible this way: tools/testbench/build_testbench/install/bin/testbench -r 48000 -R 48000 -c 2 -n 2 -b S32_LE -p 1,2 -t tools/build_tools/topology/topology2/development/ sof-hda-benchmark-dcblock32.tplg -i in.raw -o out.raw Also sof-hda-benchmark-gain32.tplg can be run. Signed-off-by: Seppo Ingalsuo --- src/arch/host/configs/library_defconfig | 1 + tools/testbench/CMakeLists.txt | 4 +- tools/testbench/common_test.c | 11 +- tools/testbench/common_test_ipc4.c | 595 ++++++++ tools/testbench/file.c | 131 +- .../testbench/include/testbench/common_test.h | 37 + tools/testbench/include/testbench/file.h | 18 + tools/testbench/include/testbench/file_ipc4.h | 26 + .../include/testbench/topology_ipc4.h | 38 + tools/testbench/topology_ipc4.c | 1280 +++++++++++++++++ uuid-registry.txt | 1 + 11 files changed, 2103 insertions(+), 39 deletions(-) create mode 100644 tools/testbench/common_test_ipc4.c create mode 100644 tools/testbench/include/testbench/file_ipc4.h create mode 100644 tools/testbench/include/testbench/topology_ipc4.h create mode 100644 tools/testbench/topology_ipc4.c diff --git a/src/arch/host/configs/library_defconfig b/src/arch/host/configs/library_defconfig index 81226d252c82..d150690aa881 100644 --- a/src/arch/host/configs/library_defconfig +++ b/src/arch/host/configs/library_defconfig @@ -22,6 +22,7 @@ CONFIG_COMP_VOLUME_LINEAR_RAMP=y CONFIG_COMP_VOLUME_WINDOWS_FADE=y CONFIG_DEBUG_MEMORY_USAGE_SCAN=n CONFIG_IPC_MAJOR_3=y +CONFIG_IPC_MAJOR_4=n CONFIG_LIBRARY=y CONFIG_LIBRARY_STATIC=y CONFIG_MATH_IIR_DF2T=y diff --git a/tools/testbench/CMakeLists.txt b/tools/testbench/CMakeLists.txt index daccb4ccaccd..179997981d94 100644 --- a/tools/testbench/CMakeLists.txt +++ b/tools/testbench/CMakeLists.txt @@ -11,10 +11,12 @@ set(default_asoc_h "/usr/include/alsa/sound/uapi/asoc.h") add_executable(testbench testbench.c + file.c common_test.c common_test_ipc3.c - file.c + common_test_ipc4.c topology_ipc3.c + topology_ipc4.c ) sof_append_relative_path_definitions(testbench) diff --git a/tools/testbench/common_test.c b/tools/testbench/common_test.c index dcbf0a353a37..6198663949f0 100644 --- a/tools/testbench/common_test.c +++ b/tools/testbench/common_test.c @@ -72,7 +72,7 @@ static int tb_find_file_helper(struct testbench_prm *tp, struct file_comp_lookup fprintf(stderr, "error: null module.\n"); return -EINVAL; } - fcd = module_get_private_data(mod); + fcd = get_file_comp_data(module_get_private_data(mod)); fcl[i].state = &fcd->fs; } @@ -101,7 +101,7 @@ static bool tb_is_file_component_at_eof(struct testbench_prm *tp) if (!tp->fr[i].state) continue; - if (tp->fr[i].state->reached_eof) + if (tp->fr[i].state->reached_eof || tp->fr[i].state->copy_timeout) return true; } @@ -109,7 +109,8 @@ static bool tb_is_file_component_at_eof(struct testbench_prm *tp) if (!tp->fw[i].state) continue; - if (tp->fw[i].state->reached_eof || tp->fw[i].state->write_failed) + if (tp->fw[i].state->reached_eof || tp->fw[i].state->copy_timeout || + tp->fw[i].state->write_failed) return true; } @@ -134,7 +135,7 @@ void tb_show_file_stats(struct testbench_prm *tp, int pipeline_id) dev = icd->cd; mod = comp_mod(dev); - fcd = module_get_private_data(mod); + fcd = get_file_comp_data(module_get_private_data(mod)); printf("file %s: id %d: type %d: samples %d copies %d\n", fcd->fs.fn, dev->ipc_config.id, dev->drv->type, fcd->fs.n, fcd->fs.copy_count); @@ -150,7 +151,7 @@ void tb_show_file_stats(struct testbench_prm *tp, int pipeline_id) dev = icd->cd; mod = comp_mod(dev); - fcd = module_get_private_data(mod); + fcd = get_file_comp_data(module_get_private_data(mod)); printf("file %s: id %d: type %d: samples %d copies %d\n", fcd->fs.fn, dev->ipc_config.id, dev->drv->type, fcd->fs.n, fcd->fs.copy_count); diff --git a/tools/testbench/common_test_ipc4.c b/tools/testbench/common_test_ipc4.c new file mode 100644 index 000000000000..8ede31d77718 --- /dev/null +++ b/tools/testbench/common_test_ipc4.c @@ -0,0 +1,595 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2018-2024 Intel Corporation. + +#if CONFIG_IPC_MAJOR_4 + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "testbench/common_test.h" +#include "testbench/file.h" +#include "testbench/topology_ipc4.h" + +#if defined __XCC__ +#include +#endif + +SOF_DEFINE_REG_UUID(testbench); +DECLARE_TR_CTX(testbench_tr, SOF_UUID(testbench_uuid), LOG_LEVEL_INFO); +LOG_MODULE_REGISTER(testbench, CONFIG_SOF_LOG_LEVEL); + +/* testbench helper functions for pipeline setup and trigger */ + +int tb_setup(struct sof *sof, struct testbench_prm *tp) +{ + struct ll_schedule_domain domain = {0}; + int bits; + int krate; + + domain.next_tick = tp->tick_period_us; + + /* init components */ + sys_comp_init(sof); + + /* Module adapter components */ + sys_comp_module_crossover_interface_init(); + sys_comp_module_dcblock_interface_init(); + sys_comp_module_demux_interface_init(); + sys_comp_module_drc_interface_init(); + sys_comp_module_eq_fir_interface_init(); + sys_comp_module_eq_iir_interface_init(); + sys_comp_module_file_interface_init(); + sys_comp_module_gain_interface_init(); + sys_comp_module_google_rtc_audio_processing_interface_init(); + sys_comp_module_igo_nr_interface_init(); + sys_comp_module_mfcc_interface_init(); + sys_comp_module_multiband_drc_interface_init(); + sys_comp_module_mux_interface_init(); + sys_comp_module_rtnr_interface_init(); + sys_comp_module_selector_interface_init(); + sys_comp_module_src_interface_init(); + sys_comp_module_asrc_interface_init(); + sys_comp_module_tdfb_interface_init(); + sys_comp_module_volume_interface_init(); + + /* other necessary initializations */ + pipeline_posn_init(sof); + init_system_notify(sof); + + /* init IPC */ + if (ipc_init(sof) < 0) { + fprintf(stderr, "error: IPC init\n"); + return -EINVAL; + } + + /* Trace */ + ipc_tr.level = LOG_LEVEL_INFO; + ipc_tr.uuid_p = SOF_UUID(testbench_uuid); + + /* init LL scheduler */ + if (scheduler_init_ll(&domain) < 0) { + fprintf(stderr, "error: edf scheduler init\n"); + return -EINVAL; + } + + /* init EDF scheduler */ + if (scheduler_init_edf() < 0) { + fprintf(stderr, "error: edf scheduler init\n"); + return -EINVAL; + } + + tb_debug_print("ipc and scheduler initialized\n"); + + /* setup IPC4 audio format */ + tp->num_configs = 1; + krate = tp->fs_in / 1000; + switch (tp->frame_fmt) { + case SOF_IPC_FRAME_S16_LE: + bits = 16; + break; + case SOF_IPC_FRAME_S24_4LE: + bits = 24; + break; + case SOF_IPC_FRAME_S32_LE: + bits = 32; + break; + default: + fprintf(stderr, "error: unsupported frame format %d\n", tp->frame_fmt); + return -EINVAL; + } + + /* TODO 44.1 kHz like rates */ + snprintf(tp->config[0].name, TB_MAX_CONFIG_NAME_SIZE, "%dk%dc%db", + krate, tp->channels_in, bits); + tp->num_configs = 1; + tp->config[0].buffer_frames = 2 * krate; + tp->config[0].buffer_time = 0; + tp->config[0].period_frames = krate; + tp->config[0].period_time = 0; + tp->config[0].rate = tp->fs_in; + tp->config[0].channels = tp->channels_in; + tp->config[0].format = tp->frame_fmt; + tp->period_size = 2 * krate; + + /* TODO: Need to set this later for larger topologies with multiple PCMs. The + * pipelines are determined based on just the PCM ID for the device that we + * want to start playback/capture on. + */ + tp->pcm_id = 0; + + return 0; +} + +static int tb_prepare_widget(struct testbench_prm *tp, struct tplg_pcm_info *pcm_info, + struct tplg_comp_info *comp_info, int dir) +{ + struct tplg_pipeline_list *pipeline_list; + int ret, i; + + if (dir) + pipeline_list = &pcm_info->capture_pipeline_list; + else + pipeline_list = &pcm_info->playback_pipeline_list; + + /* populate base config */ + ret = tb_set_up_widget_base_config(tp, comp_info); + if (ret < 0) + return ret; + + tb_pipeline_update_resource_usage(tp, comp_info); + + /* add pipeline to pcm pipeline_list if needed */ + for (i = 0; i < pipeline_list->count; i++) { + struct tplg_pipeline_info *pipe_info = pipeline_list->pipelines[i]; + + if (pipe_info == comp_info->pipe_info) + break; + } + + if (i == pipeline_list->count) { + pipeline_list->pipelines[pipeline_list->count] = comp_info->pipe_info; + pipeline_list->count++; + if (pipeline_list->count == TPLG_MAX_PCM_PIPELINES) { + fprintf(stderr, "error: pipelines count exceeds %d", + TPLG_MAX_PCM_PIPELINES); + return -EINVAL; + } + } + + return 0; +} + +static int tb_prepare_widgets_playback(struct testbench_prm *tp, struct tplg_pcm_info *pcm_info, + struct tplg_comp_info *starting_comp_info, + struct tplg_comp_info *current_comp_info) +{ + struct list_item *item; + int ret; + + /* for playback */ + list_for_item(item, &tp->route_list) { + struct tplg_route_info *route_info = container_of(item, struct tplg_route_info, + item); + + if (route_info->source != current_comp_info) + continue; + + /* set up source widget if it is the starting widget */ + if (starting_comp_info == current_comp_info) { + ret = tb_prepare_widget(tp, pcm_info, current_comp_info, 0); + if (ret < 0) + return ret; + } + + /* set up the sink widget */ + ret = tb_prepare_widget(tp, pcm_info, route_info->sink, 0); + if (ret < 0) + return ret; + + /* and then continue down the path */ + if (route_info->sink->type != SND_SOC_TPLG_DAPM_DAI_IN) { + ret = tb_prepare_widgets_playback(tp, pcm_info, starting_comp_info, + route_info->sink); + if (ret < 0) + return ret; + } + } + + return 0; +} + +static int tb_prepare_widgets_capture(struct testbench_prm *tp, struct tplg_pcm_info *pcm_info, + struct tplg_comp_info *starting_comp_info, + struct tplg_comp_info *current_comp_info) +{ + struct list_item *item; + int ret; + + /* for capture */ + list_for_item(item, &tp->route_list) { + struct tplg_route_info *route_info = container_of(item, struct tplg_route_info, + item); + + if (route_info->sink != current_comp_info) + continue; + + /* set up sink widget if it is the starting widget */ + if (starting_comp_info == current_comp_info) { + ret = tb_prepare_widget(tp, pcm_info, current_comp_info, 1); + if (ret < 0) + return ret; + } + + /* set up the source widget */ + ret = tb_prepare_widget(tp, pcm_info, route_info->source, 1); + if (ret < 0) + return ret; + + /* and then continue up the path */ + if (route_info->source->type != SND_SOC_TPLG_DAPM_DAI_OUT) { + ret = tb_prepare_widgets_capture(tp, pcm_info, starting_comp_info, + route_info->source); + if (ret < 0) + return ret; + } + } + + return 0; +} + +static int tb_set_up_widget(struct testbench_prm *tp, struct tplg_comp_info *comp_info) +{ + struct tplg_pipeline_info *pipe_info = comp_info->pipe_info; + int ret; + + pipe_info->usage_count++; + + /* first set up pipeline if needed, only done once for the first pipeline widget */ + if (pipe_info->usage_count == 1) { + ret = tb_set_up_pipeline(tp, pipe_info); + if (ret < 0) { + pipe_info->usage_count--; + return ret; + } + } + + /* now set up the widget */ + return tb_set_up_widget_ipc(tp, comp_info); +} + +static int tb_set_up_widgets_playback(struct testbench_prm *tp, + struct tplg_comp_info *starting_comp_info, + struct tplg_comp_info *current_comp_info) +{ + struct list_item *item; + int ret; + + /* for playback */ + list_for_item(item, &tp->route_list) { + struct tplg_route_info *route_info = container_of(item, struct tplg_route_info, + item); + + if (route_info->source != current_comp_info) + continue; + + /* set up source widget if it is the starting widget */ + if (starting_comp_info == current_comp_info) { + ret = tb_set_up_widget(tp, current_comp_info); + if (ret < 0) + return ret; + } + + /* set up the sink widget */ + ret = tb_set_up_widget(tp, route_info->sink); + if (ret < 0) + return ret; + + /* source and sink widgets are up, so set up route now */ + ret = tb_set_up_route(tp, route_info); + if (ret < 0) + return ret; + + /* and then continue down the path */ + if (route_info->sink->type != SND_SOC_TPLG_DAPM_DAI_IN) { + ret = tb_set_up_widgets_playback(tp, starting_comp_info, route_info->sink); + if (ret < 0) + return ret; + } + } + + return 0; +} + +static int tb_set_up_widgets_capture(struct testbench_prm *tp, + struct tplg_comp_info *starting_comp_info, + struct tplg_comp_info *current_comp_info) +{ + struct list_item *item; + int ret; + + /* for playback */ + list_for_item(item, &tp->route_list) { + struct tplg_route_info *route_info = container_of(item, struct tplg_route_info, + item); + + if (route_info->sink != current_comp_info) + continue; + + /* set up source widget if it is the starting widget */ + if (starting_comp_info == current_comp_info) { + ret = tb_set_up_widget(tp, current_comp_info); + if (ret < 0) + return ret; + } + + /* set up the sink widget */ + ret = tb_set_up_widget(tp, route_info->source); + if (ret < 0) + return ret; + + /* source and sink widgets are up, so set up route now */ + ret = tb_set_up_route(tp, route_info); + if (ret < 0) + return ret; + + /* and then continue down the path */ + if (route_info->source->type != SND_SOC_TPLG_DAPM_DAI_OUT) { + ret = tb_set_up_widgets_capture(tp, starting_comp_info, route_info->source); + if (ret < 0) + return ret; + } + } + + return 0; +} + +static int tb_set_up_pipelines(struct testbench_prm *tp, int dir) +{ + struct tplg_comp_info *host = NULL; + struct tplg_pcm_info *pcm_info; + struct list_item *item; + int ret; + + list_for_item(item, &tp->pcm_list) { + pcm_info = container_of(item, struct tplg_pcm_info, item); + + if (pcm_info->id == tp->pcm_id) { + if (dir) + host = pcm_info->capture_host; + else + host = pcm_info->playback_host; + break; + } + } + + if (!host) { + fprintf(stderr, "No host component found for PCM ID: %d\n", tp->pcm_id); + return -EINVAL; + } + + if (!tb_is_pipeline_enabled(tp, host->pipeline_id)) + return 0; + + tp->pcm_info = pcm_info; /* TODO must be an array for multiple PCMs */ + + if (dir) { + ret = tb_prepare_widgets_capture(tp, pcm_info, host, host); + if (ret < 0) + return ret; + + ret = tb_set_up_widgets_capture(tp, host, host); + if (ret < 0) + return ret; + + tb_debug_print("Setting up capture pipelines complete\n"); + + return 0; + } + + ret = tb_prepare_widgets_playback(tp, pcm_info, host, host); + if (ret < 0) + return ret; + + ret = tb_set_up_widgets_playback(tp, host, host); + if (ret < 0) + return ret; + + tb_debug_print("Setting up playback pipelines complete\n"); + + return 0; +} + +int tb_set_up_all_pipelines(struct testbench_prm *tp) +{ + int ret; + + ret = tb_set_up_pipelines(tp, SOF_IPC_STREAM_PLAYBACK); + if (ret) { + fprintf(stderr, "error: Failed tb_set_up_pipelines for playback\n"); + return ret; + } + + ret = tb_set_up_pipelines(tp, SOF_IPC_STREAM_CAPTURE); + if (ret) { + fprintf(stderr, "error: Failed tb_set_up_pipelines for capture\n"); + return ret; + } + + fprintf(stdout, "pipelines set up complete\n"); + return 0; +} + +static int tb_free_widgets_playback(struct testbench_prm *tp, + struct tplg_comp_info *starting_comp_info, + struct tplg_comp_info *current_comp_info) +{ + struct tplg_route_info *route_info; + struct list_item *item; + int ret; + + /* for playback */ + list_for_item(item, &tp->route_list) { + route_info = container_of(item, struct tplg_route_info, item); + if (route_info->source != current_comp_info) + continue; + + /* Widgets will be freed when the pipeline is deleted, so just unbind modules */ + ret = tb_free_route(tp, route_info); + if (ret < 0) + return ret; + + /* and then continue down the path */ + if (route_info->sink->type != SND_SOC_TPLG_DAPM_DAI_IN) { + ret = tb_free_widgets_playback(tp, starting_comp_info, route_info->sink); + if (ret < 0) + return ret; + } + } + + return 0; +} + +static int tb_free_widgets_capture(struct testbench_prm *tp, + struct tplg_comp_info *starting_comp_info, + struct tplg_comp_info *current_comp_info) +{ + struct tplg_route_info *route_info; + struct list_item *item; + int ret; + + /* for playback */ + list_for_item(item, &tp->route_list) { + route_info = container_of(item, struct tplg_route_info, item); + if (route_info->sink != current_comp_info) + continue; + + /* Widgets will be freed when the pipeline is deleted, so just unbind modules */ + ret = tb_free_route(tp, route_info); + if (ret < 0) + return ret; + + /* and then continue down the path */ + if (route_info->sink->type != SND_SOC_TPLG_DAPM_DAI_OUT) { + ret = tb_free_widgets_capture(tp, starting_comp_info, route_info->source); + if (ret < 0) + return ret; + } + } + + return 0; +} + +static int tb_free_pipelines(struct testbench_prm *tp, int dir) +{ + struct tplg_pipeline_list *pipeline_list; + struct tplg_pcm_info *pcm_info; + struct list_item *item; + struct tplg_comp_info *host = NULL; + int ret, i; + + list_for_item(item, &tp->pcm_list) { + pcm_info = container_of(item, struct tplg_pcm_info, item); + if (dir) + host = pcm_info->capture_host; + else + host = pcm_info->playback_host; + + if (!host || !tb_is_pipeline_enabled(tp, host->pipeline_id)) + continue; + + if (dir) { + pipeline_list = &tp->pcm_info->capture_pipeline_list; + ret = tb_free_widgets_capture(tp, host, host); + if (ret < 0) { + fprintf(stderr, "failed to free widgets for capture PCM\n"); + return ret; + } + } else { + pipeline_list = &tp->pcm_info->playback_pipeline_list; + ret = tb_free_widgets_playback(tp, host, host); + if (ret < 0) { + fprintf(stderr, "failed to free widgets for playback PCM\n"); + return ret; + } + } + for (i = 0; i < pipeline_list->count; i++) { + struct tplg_pipeline_info *pipe_info = pipeline_list->pipelines[i]; + + ret = tb_delete_pipeline(tp, pipe_info); + if (ret < 0) + return ret; + } + } + + tp->instance_ids[SND_SOC_TPLG_DAPM_SCHEDULER] = 0; + return 0; +} + +int tb_free_all_pipelines(struct testbench_prm *tp) +{ + tb_debug_print("freeing playback direction\n"); + tb_free_pipelines(tp, SOF_IPC_STREAM_PLAYBACK); + + tb_debug_print("freeing capture direction\n"); + tb_free_pipelines(tp, SOF_IPC_STREAM_CAPTURE); + return 0; +} + +void tb_free_topology(struct testbench_prm *tp) +{ + struct tplg_pcm_info *pcm_info; + struct tplg_comp_info *comp_info; + struct tplg_route_info *route_info; + struct tplg_pipeline_info *pipe_info; + struct tplg_context *ctx = &tp->tplg; + struct sof_ipc4_available_audio_format *available_fmts; + struct list_item *item, *_item; + + list_for_item_safe(item, _item, &tp->pcm_list) { + pcm_info = container_of(item, struct tplg_pcm_info, item); + list_item_del(item); + free(pcm_info->name); + free(pcm_info); + } + + list_for_item_safe(item, _item, &tp->widget_list) { + comp_info = container_of(item, struct tplg_comp_info, item); + available_fmts = &comp_info->available_fmt; + list_item_del(item); + free(available_fmts->output_pin_fmts); + free(available_fmts->input_pin_fmts); + free(comp_info->name); + free(comp_info->stream_name); + free(comp_info->ipc_payload); + free(comp_info); + } + + list_for_item_safe(item, _item, &tp->route_list) { + route_info = container_of(item, struct tplg_route_info, item); + list_item_del(item); + free(route_info); + } + + list_for_item_safe(item, _item, &tp->pipeline_list) { + pipe_info = container_of(item, struct tplg_pipeline_info, item); + list_item_del(item); + free(pipe_info->name); + free(pipe_info); + } + + free(ctx->tplg_base); + tb_debug_print("freed all pipelines, widgets, routes and pcms\n"); +} + +#endif /* CONFIG_IPC_MAJOR_4 */ diff --git a/tools/testbench/file.c b/tools/testbench/file.c index a4f87b855ac4..71934a8669e8 100644 --- a/tools/testbench/file.c +++ b/tools/testbench/file.c @@ -24,6 +24,9 @@ #include #include "testbench/common_test.h" #include "testbench/file.h" +#include "testbench/file_ipc4.h" +#include "../../src/audio/copier/copier.h" + SOF_DEFINE_REG_UUID(file); DECLARE_TR_CTX(file_tr, SOF_UUID(file_uuid), LOG_LEVEL_INFO); @@ -524,9 +527,37 @@ static enum file_format get_file_format(char *filename) return FILE_RAW; } -static int file_init_set_dai_data(struct comp_dev *dev) +#if CONFIG_IPC_MAJOR_4 +/* Minimal support for IPC4 pipeline_comp_trigger()'s dai_get_init_delay_ms() */ +static int file_init_set_dai_data(struct processing_module *mod) { struct dai_data *dd; + struct copier_data *ccd = module_get_private_data(mod); + + dd = rzalloc(SOF_MEM_ZONE_RUNTIME_SHARED, 0, SOF_MEM_CAPS_RAM, sizeof(*dd)); + if (!dd) + return -ENOMEM; + + /* Member dd->dai remains NULL. It's sufficient for dai_get_init_delay_ms(). + * In such case the functions returns zero delay. Testbench currently has + * no use for the feature. + */ + ccd->dd[0] = dd; + return 0; +} + +static void file_free_dai_data(struct processing_module *mod) +{ + struct copier_data *ccd = module_get_private_data(mod); + + free(ccd->dd[0]); +} +#else +/* Minimal support for IPC3 pipeline_comp_trigger()'s dai_get_init_delay_ms() */ +static int file_init_set_dai_data(struct processing_module *mod) +{ + struct dai_data *dd; + struct comp_dev *dev = mod->dev; dd = rzalloc(SOF_MEM_ZONE_RUNTIME_SHARED, 0, SOF_MEM_CAPS_RAM, sizeof(*dd)); if (!dd) @@ -540,30 +571,50 @@ static int file_init_set_dai_data(struct comp_dev *dev) return 0; } -static void file_free_dai_data(struct comp_dev *dev) +static void file_free_dai_data(struct processing_module *mod) { struct dai_data *dd; + struct comp_dev *dev = mod->dev; dd = comp_get_drvdata(dev); free(dd); } +#endif static int file_init(struct processing_module *mod) { struct comp_dev *dev = mod->dev; struct module_data *mod_data = &mod->priv; - const struct ipc_comp_file *ipc_file = - (const struct ipc_comp_file *)mod_data->cfg.init_data; + struct copier_data *ccd; struct file_comp_data *cd; int ret; +#if CONFIG_IPC_MAJOR_4 + const struct ipc4_file_module_cfg *module_cfg = + (const struct ipc4_file_module_cfg *)mod_data->cfg.init_data; + + const struct ipc4_file_config *ipc_file = &module_cfg->config; +#else + const struct ipc_comp_file *ipc_file = + (const struct ipc_comp_file *)mod_data->cfg.init_data; +#endif + tb_debug_print("file_init()\n"); + ccd = rzalloc(SOF_MEM_ZONE_RUNTIME_SHARED, 0, SOF_MEM_CAPS_RAM, sizeof(*ccd)); + if (!ccd) + return -ENOMEM; + + mod_data->private = ccd; + + /* File component data is placed to copier's ipcgtw_data */ cd = rzalloc(SOF_MEM_ZONE_RUNTIME_SHARED, 0, SOF_MEM_CAPS_RAM, sizeof(*cd)); - if (!cd) + if (!cd) { + free(ccd); return -ENOMEM; + } - mod_data->private = cd; + file_set_comp_data(ccd, cd); /* default function for processing samples */ cd->file_func = file_default; @@ -581,53 +632,54 @@ static int file_init(struct processing_module *mod) /* set file comp mode */ cd->fs.mode = ipc_file->mode; - cd->rate = ipc_file->rate; cd->channels = ipc_file->channels; cd->frame_fmt = ipc_file->frame_fmt; dev->direction = ipc_file->direction; + dev->direction_set = true; /* open file handle(s) depending on mode */ switch (cd->fs.mode) { case FILE_READ: + cd->fs.rfh = fopen(cd->fs.fn, "r"); + if (!cd->fs.rfh) { + fprintf(stderr, "error: opening file %s for reading - %s\n", + cd->fs.fn, strerror(errno)); + goto error; + } + /* Change to DAI type is needed to avoid uninitialized hw params in * pipeline_params, A file host can be left as SOF_COMP_MODULE_ADAPTER */ if (dev->direction == SOF_IPC_STREAM_CAPTURE) { dev->ipc_config.type = SOF_COMP_DAI; - ret = file_init_set_dai_data(dev); + ret = file_init_set_dai_data(mod); if (ret) { fprintf(stderr, "error: failed set dai data.\n"); goto error; } } - - cd->fs.rfh = fopen(cd->fs.fn, "r"); - if (!cd->fs.rfh) { - fprintf(stderr, "error: opening file %s for reading - %s\n", + break; + case FILE_WRITE: + cd->fs.wfh = fopen(cd->fs.fn, "w+"); + if (!cd->fs.wfh) { + fprintf(stderr, "error: opening file %s for writing - %s\n", cd->fs.fn, strerror(errno)); goto error; } - break; - case FILE_WRITE: + /* Change to DAI type is needed to avoid uninitialized hw params in * pipeline_params, A file host can be left as SOF_COMP_MODULE_ADAPTER */ if (dev->direction == SOF_IPC_STREAM_PLAYBACK) { dev->ipc_config.type = SOF_COMP_DAI; - ret = file_init_set_dai_data(dev); + ret = file_init_set_dai_data(mod); if (ret) { fprintf(stderr, "error: failed set dai data.\n"); goto error; } } - cd->fs.wfh = fopen(cd->fs.fn, "w+"); - if (!cd->fs.wfh) { - fprintf(stderr, "error: opening file %s for writing - %s\n", - cd->fs.fn, strerror(errno)); - goto error; - } break; default: /* TODO: duplex mode */ @@ -637,20 +689,22 @@ static int file_init(struct processing_module *mod) cd->fs.reached_eof = false; cd->fs.write_failed = false; + cd->fs.copy_timeout = false; cd->fs.n = 0; cd->fs.copy_count = 0; cd->fs.cycles_count = 0; - return 0; error: free(cd); + free(ccd); return -EINVAL; } static int file_free(struct processing_module *mod) { - struct file_comp_data *cd = module_get_private_data(mod); + struct copier_data *ccd = module_get_private_data(mod); + struct file_comp_data *cd = get_file_comp_data(ccd); tb_debug_print("file_free()"); @@ -659,9 +713,10 @@ static int file_free(struct processing_module *mod) else fclose(cd->fs.wfh); + file_free_dai_data(mod); free(cd->fs.fn); free(cd); - file_free_dai_data(mod->dev); + free(ccd); return 0; } @@ -674,13 +729,13 @@ static int file_process(struct processing_module *mod, struct output_stream_buffer *output_buffers, int num_output_buffers) { struct comp_dev *dev = mod->dev; - struct file_comp_data *cd = module_get_private_data(mod); + struct file_comp_data *cd = get_file_comp_data(module_get_private_data(mod)); struct audio_stream *source; struct audio_stream *sink; struct comp_buffer *buffer; uint32_t frames; uint64_t cycles0, cycles1; - int samples; + int samples = 0; int ret = 0; if (cd->fs.reached_eof) @@ -715,9 +770,18 @@ static int file_process(struct processing_module *mod, cd->fs.copy_count++; if (cd->fs.reached_eof || (cd->max_copies && cd->fs.copy_count >= cd->max_copies)) { - cd->fs.reached_eof = 1; + cd->fs.reached_eof = true; tb_debug_print("file_process(): reached EOF"); - schedule_task_cancel(mod->dev->pipeline->pipe_task); + } + + if (samples) { + cd->copies_timeout_count = 0; + } else { + cd->copies_timeout_count++; + if (cd->copies_timeout_count == FILE_MAX_COPIES_TIMEOUT) { + tb_debug_print("file_process(): copies_timeout reached\n"); + cd->fs.copy_timeout = true; + } } tb_getcycles(&cycles1); @@ -732,7 +796,7 @@ static int file_prepare(struct processing_module *mod, struct audio_stream *stream; struct comp_buffer *buffer; struct comp_dev *dev = mod->dev; - struct file_comp_data *cd = module_get_private_data(mod); + struct file_comp_data *cd = get_file_comp_data(module_get_private_data(mod)); tb_debug_print("file_prepare()"); @@ -774,15 +838,16 @@ static int file_prepare(struct processing_module *mod, static int file_reset(struct processing_module *mod) { - tb_debug_print("file_reset()"); + struct file_comp_data *cd = module_get_private_data(mod); + tb_debug_print("file_reset()"); + cd->copies_timeout_count = 0; return 0; } static int file_trigger(struct comp_dev *dev, int cmd) { - tb_debug_print("asrc_trigger()"); - + tb_debug_print("file_trigger()"); return comp_set_state(dev, cmd); } @@ -790,7 +855,7 @@ static int file_get_hw_params(struct comp_dev *dev, struct sof_ipc_stream_params *params, int dir) { struct processing_module *mod = comp_mod(dev); - struct file_comp_data *cd = module_get_private_data(mod); + struct file_comp_data *cd = get_file_comp_data(module_get_private_data(mod)); tb_debug_print("file_hw_params()"); params->direction = dir; diff --git a/tools/testbench/include/testbench/common_test.h b/tools/testbench/include/testbench/common_test.h index 7245eacba367..801d9981a3b9 100644 --- a/tools/testbench/include/testbench/common_test.h +++ b/tools/testbench/include/testbench/common_test.h @@ -32,6 +32,28 @@ struct file_comp_lookup { struct file_state *state; }; +#if CONFIG_IPC_MAJOR_4 + +#define TB_NAME_SIZE 256 +#define TB_MAX_CONFIG_COUNT 2 +#define TB_MAX_CONFIG_NAME_SIZE 64 + +struct tb_mq_desc { + char queue_name[TB_NAME_SIZE]; +}; + +struct tb_config { + char name[TB_MAX_CONFIG_NAME_SIZE]; + unsigned long buffer_frames; + unsigned long buffer_time; + unsigned long period_frames; + unsigned long period_time; + int rate; + int channels; + unsigned long format; +}; +#endif + /* * Global testbench data. * @@ -78,6 +100,21 @@ struct testbench_prm { /* topology */ struct tplg_context tplg; + +#if CONFIG_IPC_MAJOR_4 + struct list_item widget_list; + struct list_item route_list; + struct list_item pcm_list; + struct list_item pipeline_list; + int instance_ids[SND_SOC_TPLG_DAPM_LAST]; + struct tb_mq_desc ipc_tx; + struct tb_mq_desc ipc_rx; + int pcm_id; // TODO: This needs to be cleaned up + struct tplg_pcm_info *pcm_info; + struct tb_config config[TB_MAX_CONFIG_COUNT]; + int num_configs; + size_t period_size; +#endif }; extern int debug; diff --git a/tools/testbench/include/testbench/file.h b/tools/testbench/include/testbench/file.h index 438c7b519d21..869772b5ee62 100644 --- a/tools/testbench/include/testbench/file.h +++ b/tools/testbench/include/testbench/file.h @@ -13,6 +13,8 @@ #include +#define FILE_MAX_COPIES_TIMEOUT 3 + /**< Convert with right shift a bytes count to samples count */ #define FILE_BYTES_TO_S16_SAMPLES(s) ((s) >> 1) #define FILE_BYTES_TO_S32_SAMPLES(s) ((s) >> 2) @@ -40,6 +42,7 @@ struct file_state { enum file_format f_format; bool reached_eof; bool write_failed; + bool copy_timeout; }; struct file_comp_data; @@ -58,8 +61,23 @@ struct file_comp_data { int max_samples; int max_copies; int max_frames; + int copies_timeout_count; }; void sys_comp_module_file_interface_init(void); +/* Get file comp data from copier data */ +static inline struct file_comp_data *get_file_comp_data(struct copier_data *ccd) +{ + struct file_comp_data *cd = (struct file_comp_data *)ccd->ipcgtw_data; + + return cd; +} + +/* Set file comp data to copier data */ +static inline void file_set_comp_data(struct copier_data *ccd, struct file_comp_data *cd) +{ + ccd->ipcgtw_data = (struct ipcgtw_data *)cd; +} + #endif /* _TESTBENCH_FILE */ diff --git a/tools/testbench/include/testbench/file_ipc4.h b/tools/testbench/include/testbench/file_ipc4.h new file mode 100644 index 000000000000..c9e435b3883f --- /dev/null +++ b/tools/testbench/include/testbench/file_ipc4.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2024 Intel Corporation. + */ + +#ifndef __TESTBENCH_FILE_IPC4_H_ +#define __TESTBENCH_FILE_IPC4_H_ + +#include +#include + +struct ipc4_file_config { + uint32_t rate; + uint32_t channels; + char *fn; + uint32_t mode; + uint32_t frame_fmt; + uint32_t direction; /**< SOF_IPC_STREAM_ */ +}; + +struct ipc4_file_module_cfg { + struct ipc4_base_module_cfg base_cfg; + struct ipc4_file_config config; +} __packed __aligned(8); + +#endif /* __TESTBENCH_FILE_IPC4_H_ */ diff --git a/tools/testbench/include/testbench/topology_ipc4.h b/tools/testbench/include/testbench/topology_ipc4.h new file mode 100644 index 000000000000..c9369862d402 --- /dev/null +++ b/tools/testbench/include/testbench/topology_ipc4.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2024 Intel Corporation. + */ + +#ifndef _TESTBENCH_TOPOLOGY_IPC4_H +#define _TESTBENCH_TOPOLOGY_IPC4_H + +#include +#include "testbench/common_test.h" + +#define TB_IPC4_MAX_TPLG_OBJECT_SIZE 4096 +#define TB_IPC4_MAX_MSG_SIZE 384 + +int tb_delete_pipeline(struct testbench_prm *tp, struct tplg_pipeline_info *pipe_info); +int tb_free_all_pipelines(struct testbench_prm *tp); +int tb_free_route(struct testbench_prm *tp, struct tplg_route_info *route_info); +int tb_get_instance_id_from_pipeline_id(struct testbench_prm *tp, int id); +int tb_is_single_format(struct sof_ipc4_pin_format *fmts, int num_formats); +int tb_match_audio_format(struct testbench_prm *tp, struct tplg_comp_info *comp_info, + struct tb_config *config); +int tb_new_aif_in_out(struct testbench_prm *tp, int dir); +int tb_new_dai_in_out(struct testbench_prm *tp, int dir); +int tb_new_pga(struct testbench_prm *tp); +int tb_new_process(struct testbench_prm *tp); +int tb_pipelines_set_state(struct testbench_prm *tp, int state, int dir); +int tb_set_reset_state(struct testbench_prm *tp); +int tb_set_running_state(struct testbench_prm *tp); +int tb_set_up_pipeline(struct testbench_prm *tp, struct tplg_pipeline_info *pipe_info); +int tb_set_up_route(struct testbench_prm *tp, struct tplg_route_info *route_info); +int tb_set_up_widget_base_config(struct testbench_prm *tp, + struct tplg_comp_info *comp_info); +int tb_set_up_widget_ipc(struct testbench_prm *tp, struct tplg_comp_info *comp_info); +void tb_free_topology(struct testbench_prm *tp); +void tb_pipeline_update_resource_usage(struct testbench_prm *tp, + struct tplg_comp_info *comp_info); + +#endif /* _TESTBENCH_TOPOLOGY_IPC4_H */ diff --git a/tools/testbench/topology_ipc4.c b/tools/testbench/topology_ipc4.c new file mode 100644 index 000000000000..9020b5b03634 --- /dev/null +++ b/tools/testbench/topology_ipc4.c @@ -0,0 +1,1280 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2018-2024 Intel Corporation, +// +// Author: Ranjani Sridharan +// Liam Girdwood +// Seppo Ingalsuo + +#if CONFIG_IPC_MAJOR_4 + +#include +#include +#include +#include +#include + +#include "testbench/common_test.h" +#include "testbench/topology_ipc4.h" +#include "testbench/file.h" +#include "testbench/file_ipc4.h" +#include "ipc4/pipeline.h" + +#define SOF_IPC4_FW_PAGE(x) ((((x) + BIT(12) - 1) & ~(BIT(12) - 1)) >> 12) +#define SOF_IPC4_FW_ROUNDUP(x) (((x) + BIT(6) - 1) & (~(BIT(6) - 1))) +#define SOF_IPC4_MODULE_INSTANCE_LIST_ITEM_SIZE 12 +#define SOF_IPC4_PIPELINE_OBJECT_SIZE 448 +#define SOF_IPC4_DATA_QUEUE_OBJECT_SIZE 128 +#define SOF_IPC4_LL_TASK_OBJECT_SIZE 72 +#define SOF_IPC4_LL_TASK_LIST_ITEM_SIZE 12 +#define SOF_IPC4_FW_MAX_QUEUE_COUNT 8 + +static const struct sof_topology_token ipc4_comp_tokens[] = { + {SOF_TKN_COMP_IS_PAGES, SND_SOC_TPLG_TUPLE_TYPE_WORD, tplg_token_get_uint32_t, + offsetof(struct ipc4_base_module_cfg, is_pages)}, +}; + +/* + * IPC + */ + +static int tb_ipc_message(void *mailbox, size_t bytes) +{ + struct ipc *ipc = ipc_get(); + + /* reply is copied back to mailbox */ + memcpy(ipc->comp_data, mailbox, bytes); + ipc_cmd(mailbox); + memcpy(mailbox, ipc->comp_data, bytes); + + return 0; +} + +static int tb_mq_cmd_tx_rx(struct tb_mq_desc *ipc_tx, struct tb_mq_desc *ipc_rx, + void *msg, size_t len, void *reply, size_t rlen) +{ + char mailbox[TB_IPC4_MAX_MSG_SIZE]; + struct ipc4_message_reply *reply_msg = reply; + + if (len > TB_IPC4_MAX_MSG_SIZE || rlen > TB_IPC4_MAX_MSG_SIZE) { + fprintf(stderr, "ipc: message too big len=%zu rlen=%zu\n", len, rlen); + return -EINVAL; + } + + memset(mailbox, 0, TB_IPC4_MAX_MSG_SIZE); + memcpy(mailbox, msg, len); + tb_ipc_message(mailbox, len); + memcpy(reply, mailbox, rlen); + if (reply_msg->primary.r.status != IPC4_SUCCESS) + return -EINVAL; + + return 0; +} + +static int tb_parse_ipc4_comp_tokens(struct testbench_prm *tp, + struct ipc4_base_module_cfg *base_cfg) +{ + struct tplg_context *ctx = &tp->tplg; + struct tplg_comp_info *comp_info = ctx->current_comp_info; + struct snd_soc_tplg_vendor_array *array = &ctx->widget->priv.array[0]; + int size = ctx->widget->priv.size; + int ret; + + ret = sof_parse_token_sets(base_cfg, ipc4_comp_tokens, ARRAY_SIZE(ipc4_comp_tokens), + array, size, 1, 0); + if (ret < 0) + return ret; + + return sof_parse_tokens(&comp_info->uuid, comp_ext_tokens, + ARRAY_SIZE(comp_ext_tokens), array, size); +} + +static void tb_setup_widget_ipc_msg(struct tplg_comp_info *comp_info) +{ + struct ipc4_module_init_instance *module_init = &comp_info->module_init; + + module_init->primary.r.type = SOF_IPC4_MOD_INIT_INSTANCE; + module_init->primary.r.module_id = comp_info->module_id; + module_init->primary.r.instance_id = comp_info->instance_id; + module_init->primary.r.msg_tgt = SOF_IPC4_MESSAGE_TARGET_MODULE_MSG; + module_init->primary.r.rsp = SOF_IPC4_MESSAGE_DIR_MSG_REQUEST; +} + +int tb_set_up_widget_ipc(struct testbench_prm *tp, struct tplg_comp_info *comp_info) +{ + struct ipc4_module_init_instance *module_init = &comp_info->module_init; + struct ipc4_message_reply reply; + void *msg; + int size; + int ret = 0; + + module_init->extension.r.param_block_size = comp_info->ipc_size >> 2; + module_init->extension.r.ppl_instance_id = comp_info->pipe_info->instance_id; + + size = sizeof(*module_init) + comp_info->ipc_size; + msg = calloc(size, 1); + if (!msg) + return -ENOMEM; + + memcpy(msg, module_init, sizeof(*module_init)); + memcpy(msg + sizeof(*module_init), comp_info->ipc_payload, comp_info->ipc_size); + ret = tb_mq_cmd_tx_rx(&tp->ipc_tx, &tp->ipc_rx, msg, size, &reply, sizeof(reply)); + + free(msg); + if (ret < 0) { + fprintf(stderr, "error: can't set up widget %s\n", comp_info->name); + return ret; + } + + if (reply.primary.r.status != IPC4_SUCCESS) { + fprintf(stderr, "widget %s set up failed with status %d\n", + comp_info->name, reply.primary.r.status); + return -EINVAL; + } + return 0; +} + +int tb_set_up_route(struct testbench_prm *tp, struct tplg_route_info *route_info) +{ + struct tplg_comp_info *src_comp_info = route_info->source; + struct tplg_comp_info *sink_comp_info = route_info->sink; + struct ipc4_module_bind_unbind bu = {{0}}; + struct ipc4_message_reply reply; + int ret; + + bu.primary.r.module_id = src_comp_info->module_id; + bu.primary.r.instance_id = src_comp_info->instance_id; + bu.primary.r.type = SOF_IPC4_MOD_BIND; + bu.primary.r.msg_tgt = SOF_IPC4_MESSAGE_TARGET_MODULE_MSG; + bu.primary.r.rsp = SOF_IPC4_MESSAGE_DIR_MSG_REQUEST; + + bu.extension.r.dst_module_id = sink_comp_info->module_id; + bu.extension.r.dst_instance_id = sink_comp_info->instance_id; + + /* FIXME: assign queue ID for components with multiple inputs/outputs */ + bu.extension.r.dst_queue = 0; + bu.extension.r.src_queue = 0; + + ret = tb_mq_cmd_tx_rx(&tp->ipc_tx, &tp->ipc_rx, &bu, sizeof(bu), &reply, sizeof(reply)); + if (ret < 0) { + fprintf(stderr, "error: can't set up route %s -> %s\n", src_comp_info->name, + sink_comp_info->name); + return ret; + } + + if (reply.primary.r.status != IPC4_SUCCESS) { + fprintf(stderr, "route %s -> %s ID set up failed with status %d\n", + src_comp_info->name, sink_comp_info->name, reply.primary.r.status); + return -EINVAL; + } + + tplg_debug("route %s -> %s set up\n", src_comp_info->name, sink_comp_info->name); + + return 0; +} + +int tb_set_up_pipeline(struct testbench_prm *tp, struct tplg_pipeline_info *pipe_info) +{ + struct ipc4_pipeline_create msg = {.primary.dat = 0, .extension.dat = 0}; + struct ipc4_message_reply reply; + int ret; + + msg.primary.r.type = SOF_IPC4_GLB_CREATE_PIPELINE; + msg.primary.r.msg_tgt = SOF_IPC4_MESSAGE_TARGET_FW_GEN_MSG; + msg.primary.r.rsp = SOF_IPC4_MESSAGE_DIR_MSG_REQUEST; + pipe_info->instance_id = tp->instance_ids[SND_SOC_TPLG_DAPM_SCHEDULER]++; + msg.primary.r.instance_id = pipe_info->instance_id; + msg.primary.r.ppl_mem_size = pipe_info->mem_usage; + ret = tb_mq_cmd_tx_rx(&tp->ipc_tx, &tp->ipc_rx, &msg, sizeof(msg), &reply, sizeof(reply)); + if (ret < 0) { + fprintf(stderr, "error: can't set up pipeline %s\n", pipe_info->name); + return ret; + } + + if (reply.primary.r.status != IPC4_SUCCESS) { + fprintf(stderr, "pipeline %s instance ID %d set up failed with status %d\n", + pipe_info->name, pipe_info->instance_id, reply.primary.r.status); + return -EINVAL; + } + + tplg_debug("pipeline %s instance_id %d mem_usage %d set up\n", pipe_info->name, + pipe_info->instance_id, pipe_info->mem_usage); + + return 0; +} + +void tb_pipeline_update_resource_usage(struct testbench_prm *tp, + struct tplg_comp_info *comp_info) +{ + struct ipc4_base_module_cfg *base_config = &comp_info->basecfg; + struct tplg_pipeline_info *pipe_info = comp_info->pipe_info; + int task_mem, queue_mem; + int ibs, bss, total; + + ibs = base_config->ibs; + bss = base_config->is_pages; + + task_mem = SOF_IPC4_PIPELINE_OBJECT_SIZE; + task_mem += SOF_IPC4_MODULE_INSTANCE_LIST_ITEM_SIZE + bss; + + /* LL modules */ + task_mem += SOF_IPC4_FW_ROUNDUP(SOF_IPC4_LL_TASK_OBJECT_SIZE); + task_mem += SOF_IPC4_FW_MAX_QUEUE_COUNT * SOF_IPC4_MODULE_INSTANCE_LIST_ITEM_SIZE; + task_mem += SOF_IPC4_LL_TASK_LIST_ITEM_SIZE; + + ibs = SOF_IPC4_FW_ROUNDUP(ibs); + queue_mem = SOF_IPC4_FW_MAX_QUEUE_COUNT * (SOF_IPC4_DATA_QUEUE_OBJECT_SIZE + ibs); + + total = SOF_IPC4_FW_PAGE(task_mem + queue_mem); + + pipe_info->mem_usage += total; +} + +/* + * IPC + */ + +int tb_is_single_format(struct sof_ipc4_pin_format *fmts, int num_formats) +{ + struct sof_ipc4_pin_format *fmt = &fmts[0]; + uint32_t _rate, _channels, _valid_bits; + int i; + + if (!fmt) { + fprintf(stderr, "Error: Null fmt\n"); + return false; + } + + _rate = fmt->audio_fmt.sampling_frequency; + _channels = fmt->audio_fmt.fmt_cfg & MASK(7, 0); + _valid_bits = (fmt->audio_fmt.fmt_cfg & MASK(15, 8)) >> 8; + for (i = 1; i < num_formats; i++) { + struct sof_ipc4_pin_format *fmt = &fmts[i]; + uint32_t rate, channels, valid_bits; + + rate = fmt->audio_fmt.sampling_frequency; + channels = fmt->audio_fmt.fmt_cfg & MASK(7, 0); + valid_bits = (fmt->audio_fmt.fmt_cfg & MASK(15, 8)) >> 8; + if (rate != _rate || channels != _channels || valid_bits != _valid_bits) + return false; + } + + return true; +} + +int tb_match_audio_format(struct testbench_prm *tp, struct tplg_comp_info *comp_info, + struct tb_config *config) +{ + struct sof_ipc4_available_audio_format *available_fmt = &comp_info->available_fmt; + struct ipc4_base_module_cfg *base_cfg = &comp_info->basecfg; + struct sof_ipc4_pin_format *fmt; + int config_valid_bits; + int i; + + switch (config->format) { + case SOF_IPC_FRAME_S16_LE: + config_valid_bits = 16; + break; + case SOF_IPC_FRAME_S32_LE: + config_valid_bits = 32; + break; + case SOF_IPC_FRAME_S24_4LE: + config_valid_bits = 24; + break; + default: + return -EINVAL; + } + + if (tb_is_single_format(available_fmt->input_pin_fmts, + available_fmt->num_input_formats)) { + fmt = &available_fmt->input_pin_fmts[0]; + goto out; + } + + for (i = 0; i < available_fmt->num_input_formats; i++) { + uint32_t rate, channels, valid_bits; + + fmt = &available_fmt->input_pin_fmts[i]; + + rate = fmt->audio_fmt.sampling_frequency; + channels = fmt->audio_fmt.fmt_cfg & MASK(7, 0); + valid_bits = (fmt->audio_fmt.fmt_cfg & MASK(15, 8)) >> 8; + + if (rate == config->rate && channels == config->channels && + valid_bits == config_valid_bits) + break; + } + + if (i == available_fmt->num_input_formats) { + fprintf(stderr, + "Cannot find matching format for rate %d channels %d valid_bits %d for %s\n", + config->rate, config->channels, config_valid_bits, comp_info->name); + return -EINVAL; + } +out: + + base_cfg->audio_fmt.sampling_frequency = fmt->audio_fmt.sampling_frequency; + base_cfg->audio_fmt.depth = fmt->audio_fmt.bit_depth; + base_cfg->audio_fmt.ch_map = fmt->audio_fmt.ch_map; + base_cfg->audio_fmt.ch_cfg = fmt->audio_fmt.ch_cfg; + base_cfg->audio_fmt.interleaving_style = fmt->audio_fmt.interleaving_style; + base_cfg->audio_fmt.channels_count = fmt->audio_fmt.fmt_cfg & MASK(7, 0); + base_cfg->audio_fmt.valid_bit_depth = + (fmt->audio_fmt.fmt_cfg & MASK(15, 8)) >> 8; + base_cfg->audio_fmt.s_type = + (fmt->audio_fmt.fmt_cfg & MASK(23, 16)) >> 16; + base_cfg->ibs = tp->period_size * 2; + base_cfg->obs = tp->period_size * 2; + + return 0; +} + +int tb_set_up_widget_base_config(struct testbench_prm *tp, struct tplg_comp_info *comp_info) +{ + char *config_name = tp->config[0].name; + struct tb_config *config; + bool config_found = false; + int ret, i; + + for (i = 0; i < tp->num_configs; i++) { + config = &tp->config[i]; + + if (!strcmp(config->name, config_name)) { + config_found = true; + break; + } + } + + if (!config_found) { + fprintf(stderr, "unsupported config requested %s\n", config_name); + return -ENOTSUP; + } + + /* match audio formats and populate base config */ + ret = tb_match_audio_format(tp, comp_info, config); + if (ret < 0) + return ret; + + /* copy the basecfg into the ipc payload */ + memcpy(comp_info->ipc_payload, &comp_info->basecfg, sizeof(struct ipc4_base_module_cfg)); + + return 0; +} + +static int tb_pipeline_set_state(struct testbench_prm *tp, int state, + struct ipc4_pipeline_set_state *pipe_state, + struct tplg_pipeline_info *pipe_info, + struct tb_mq_desc *ipc_tx, struct tb_mq_desc *ipc_rx) +{ + struct ipc4_message_reply reply = {{ 0 }}; + int ret; + + pipe_state->primary.r.ppl_id = pipe_info->instance_id; + + ret = tb_mq_cmd_tx_rx(ipc_tx, ipc_rx, pipe_state, sizeof(*pipe_state), + &reply, sizeof(reply)); + if (ret < 0) + fprintf(stderr, "failed pipeline %d set state %d\n", pipe_info->instance_id, state); + + return ret; +} + +int tb_pipelines_set_state(struct testbench_prm *tp, int state, int dir) +{ + struct ipc4_pipeline_set_state pipe_state = {{ 0 }}; + struct tplg_pipeline_list *pipeline_list; + struct tplg_pipeline_info *pipe_info; + int ret; + int i; + + if (dir == SOF_IPC_STREAM_CAPTURE) + pipeline_list = &tp->pcm_info->capture_pipeline_list; + else + pipeline_list = &tp->pcm_info->playback_pipeline_list; + + pipe_state.primary.r.ppl_state = state; + pipe_state.primary.r.type = SOF_IPC4_GLB_SET_PIPELINE_STATE; + pipe_state.primary.r.msg_tgt = SOF_IPC4_MESSAGE_TARGET_FW_GEN_MSG; + pipe_state.primary.r.rsp = SOF_IPC4_MESSAGE_DIR_MSG_REQUEST; + + /* + * pipeline list is populated starting from the host to DAI. So traverse the list in + * the reverse order for capture to start the source pipeline first. + */ + if (dir == SOF_IPC_STREAM_CAPTURE) { + for (i = pipeline_list->count - 1; i >= 0; i--) { + pipe_info = pipeline_list->pipelines[i]; + ret = tb_pipeline_set_state(tp, state, &pipe_state, pipe_info, + &tp->ipc_tx, &tp->ipc_rx); + if (ret < 0) + return ret; + } + + return 0; + } + + for (i = 0; i < pipeline_list->count; i++) { + pipe_info = pipeline_list->pipelines[i]; + ret = tb_pipeline_set_state(tp, state, &pipe_state, pipe_info, + &tp->ipc_tx, &tp->ipc_rx); + if (ret < 0) + return ret; + } + + return 0; +} + +/* + * Topology widgets + */ + +static int tb_new_src(struct testbench_prm *tp) +{ + struct tplg_context *ctx = &tp->tplg; + char tplg_object[TB_IPC4_MAX_TPLG_OBJECT_SIZE] = {0}; + struct sof_ipc_comp_src *src = (struct sof_ipc_comp_src *)tplg_object; + struct snd_soc_tplg_ctl_hdr *tplg_ctl; + int ret; + + tplg_ctl = calloc(ctx->hdr->payload_size, 1); + if (!tplg_ctl) + return -ENOMEM; + + ret = tplg_new_src(ctx, &src->comp, TB_IPC4_MAX_TPLG_OBJECT_SIZE, + tplg_ctl, ctx->hdr->payload_size); + if (ret < 0) + fprintf(stderr, "error: failed to create SRC\n"); + + free(tplg_ctl); + return ret; +} + +static int tb_new_asrc(struct testbench_prm *tp) +{ + struct tplg_context *ctx = &tp->tplg; + char tplg_object[TB_IPC4_MAX_TPLG_OBJECT_SIZE] = {0}; + struct sof_ipc_comp_asrc *asrc = (struct sof_ipc_comp_asrc *)tplg_object; + struct snd_soc_tplg_ctl_hdr *tplg_ctl; + int ret; + + tplg_ctl = calloc(ctx->hdr->payload_size, 1); + if (!tplg_ctl) + return -ENOMEM; + + ret = tplg_new_asrc(ctx, &asrc->comp, TB_IPC4_MAX_TPLG_OBJECT_SIZE, + tplg_ctl, ctx->hdr->payload_size); + if (ret < 0) + fprintf(stderr, "error: failed to create ASRC\n"); + + free(tplg_ctl); + return ret; +} + +static int tb_new_mixer(struct testbench_prm *tp) +{ + struct tplg_context *ctx = &tp->tplg; + struct tplg_comp_info *comp_info = ctx->current_comp_info; + char tplg_object[TB_IPC4_MAX_TPLG_OBJECT_SIZE] = {0}; + struct sof_ipc_comp_mixer *mixer = (struct sof_ipc_comp_mixer *)tplg_object; + struct snd_soc_tplg_ctl_hdr *tplg_ctl; + int ret; + + tplg_ctl = calloc(ctx->hdr->payload_size, 1); + if (!tplg_ctl) + return -ENOMEM; + + comp_info->instance_id = tp->instance_ids[SND_SOC_TPLG_DAPM_MIXER]++; + comp_info->ipc_size = sizeof(struct ipc4_base_module_cfg); + comp_info->ipc_payload = calloc(comp_info->ipc_size, 1); + if (!comp_info->ipc_payload) + return -ENOMEM; + + ret = tplg_new_mixer(ctx, &mixer->comp, TB_IPC4_MAX_TPLG_OBJECT_SIZE, + tplg_ctl, ctx->hdr->payload_size); + if (ret < 0) { + fprintf(stderr, "error: failed to create mixer\n"); + goto out; + } + + if (strstr(comp_info->name, "mixin")) { + comp_info->module_id = 0x2; + tb_setup_widget_ipc_msg(comp_info); + } else { + comp_info->module_id = 0x3; + tb_setup_widget_ipc_msg(comp_info); + } +out: + free(tplg_ctl); + return ret; +} + +static int tb_new_pipeline(struct testbench_prm *tp) +{ + struct tplg_pipeline_info *pipe_info; + struct sof_ipc_pipe_new pipeline = {{0}}; + struct snd_soc_tplg_ctl_hdr *tplg_ctl; + struct tplg_context *ctx = &tp->tplg; + int ret = 0; + + tplg_ctl = calloc(ctx->hdr->payload_size, 1); + if (!tplg_ctl) + return -ENOMEM; + + pipe_info = calloc(sizeof(struct tplg_pipeline_info), 1); + if (!pipe_info) { + ret = -ENOMEM; + goto out; + } + + pipe_info->name = strdup(ctx->widget->name); + if (!pipe_info->name) { + free(pipe_info); + goto out; + } + + pipe_info->id = ctx->pipeline_id; + + ret = tplg_new_pipeline(ctx, &pipeline, sizeof(pipeline), tplg_ctl); + if (ret < 0) { + fprintf(stderr, "error: failed to create pipeline\n"); + free(pipe_info->name); + free(pipe_info); + goto out; + } + + list_item_append(&pipe_info->item, &tp->pipeline_list); + tplg_debug("loading pipeline %s\n", pipe_info->name); +out: + free(tplg_ctl); + return ret; +} + +static int tb_new_buffer(struct testbench_prm *tp) +{ + struct ipc4_copier_module_cfg *copier = calloc(sizeof(struct ipc4_copier_module_cfg), 1); + struct tplg_context *ctx = &tp->tplg; + struct tplg_comp_info *comp_info = ctx->current_comp_info; + int ret; + + if (!copier) + return -ENOMEM; + + comp_info->ipc_payload = copier; + + ret = tplg_new_buffer(ctx, copier, sizeof(copier), NULL, 0); + if (ret < 0) { + fprintf(stderr, "error: failed to create pipeline\n"); + free(copier); + } + + return ret; +} + +int tb_new_aif_in_out(struct testbench_prm *tp, int dir) +{ + struct tplg_context *ctx = &tp->tplg; + struct tplg_comp_info *comp_info = ctx->current_comp_info; + struct ipc4_file_module_cfg *file; + int ret; + + ret = tplg_parse_widget_audio_formats(ctx); + if (ret < 0) + return ret; + + comp_info->ipc_payload = calloc(sizeof(struct ipc4_file_module_cfg), 1); + if (!comp_info->ipc_payload) + return -ENOMEM; + + comp_info->ipc_size = sizeof(struct ipc4_file_module_cfg); + + if (dir == SOF_IPC_STREAM_PLAYBACK) { + /* Set from testbench command line*/ + file = (struct ipc4_file_module_cfg *)comp_info->ipc_payload; + file->config.mode = FILE_READ; + file->config.rate = tp->fs_in; + file->config.channels = tp->channels_in; + file->config.frame_fmt = tp->frame_fmt; + file->config.direction = dir; + + comp_info->instance_id = tp->instance_ids[SND_SOC_TPLG_DAPM_AIF_IN]++; + comp_info->module_id = 0x9a; + if (tb_is_pipeline_enabled(tp, ctx->pipeline_id)) { + if (tp->input_file_index >= tp->input_file_num) { + fprintf(stderr, "error: not enough input files for aif\n"); + return -EINVAL; + } + file->config.fn = tp->input_file[tp->input_file_index]; + tp->fr[tp->input_file_index].id = comp_info->module_id; + tp->fr[tp->input_file_index].instance_id = comp_info->instance_id; + tp->fr[tp->input_file_index].pipeline_id = ctx->pipeline_id; + tp->input_file_index++; + } + + tb_setup_widget_ipc_msg(comp_info); + } else { + file = (struct ipc4_file_module_cfg *)comp_info->ipc_payload; + file->config.mode = FILE_WRITE; + file->config.rate = tp->fs_out; + file->config.channels = tp->channels_out; + file->config.frame_fmt = tp->frame_fmt; + file->config.direction = dir; + + comp_info->instance_id = tp->instance_ids[SND_SOC_TPLG_DAPM_AIF_OUT]++; + comp_info->module_id = 0x9b; + if (tb_is_pipeline_enabled(tp, ctx->pipeline_id)) { + if (tp->output_file_index >= tp->output_file_num) { + fprintf(stderr, "error: not enough output files for aif\n"); + return -EINVAL; + } + file->config.fn = tp->output_file[tp->output_file_index]; + tp->fw[tp->output_file_index].id = comp_info->module_id; + tp->fw[tp->output_file_index].instance_id = comp_info->instance_id; + tp->fw[tp->output_file_index].pipeline_id = ctx->pipeline_id; + tp->output_file_index++; + } + tb_setup_widget_ipc_msg(comp_info); + } + + return 0; +} + +int tb_new_dai_in_out(struct testbench_prm *tp, int dir) +{ + struct tplg_context *ctx = &tp->tplg; + struct tplg_comp_info *comp_info = ctx->current_comp_info; + struct ipc4_file_module_cfg *file; + int ret; + + ret = tplg_parse_widget_audio_formats(ctx); + if (ret < 0) + return ret; + + comp_info->ipc_payload = calloc(sizeof(struct ipc4_file_module_cfg), 1); + if (!comp_info->ipc_payload) + return -ENOMEM; + + comp_info->ipc_size = sizeof(struct ipc4_file_module_cfg); + + if (dir == SOF_IPC_STREAM_PLAYBACK) { + /* Set from testbench command line*/ + file = (struct ipc4_file_module_cfg *)comp_info->ipc_payload; + file->config.mode = FILE_WRITE; + file->config.rate = tp->fs_out; + file->config.channels = tp->channels_out; + file->config.frame_fmt = tp->frame_fmt; + file->config.direction = dir; + + comp_info->instance_id = tp->instance_ids[SND_SOC_TPLG_DAPM_DAI_OUT]++; + comp_info->module_id = 0x9c; + if (tb_is_pipeline_enabled(tp, ctx->pipeline_id)) { + if (tp->output_file_index >= tp->output_file_num) { + fprintf(stderr, "error: not enough output files for dai\n"); + return -EINVAL; + } + file->config.fn = tp->output_file[tp->output_file_index]; + tp->fw[tp->output_file_index].id = comp_info->module_id; + tp->fw[tp->output_file_index].instance_id = comp_info->instance_id; + tp->fw[tp->output_file_index].pipeline_id = ctx->pipeline_id; + tp->output_file_index++; + } + tb_setup_widget_ipc_msg(comp_info); + } else { + file = (struct ipc4_file_module_cfg *)comp_info->ipc_payload; + file->config.mode = FILE_READ; + file->config.rate = tp->fs_in; + file->config.channels = tp->channels_in; + file->config.frame_fmt = tp->frame_fmt; + file->config.direction = dir; + + comp_info->instance_id = tp->instance_ids[SND_SOC_TPLG_DAPM_DAI_IN]++; + comp_info->module_id = 0x9d; + if (tb_is_pipeline_enabled(tp, ctx->pipeline_id)) { + if (tp->input_file_index >= tp->input_file_num) { + fprintf(stderr, "error: not enough input files for dai\n"); + return -EINVAL; + } + file->config.fn = tp->input_file[tp->input_file_index]; + tp->fr[tp->input_file_index].id = comp_info->module_id; + tp->fr[tp->input_file_index].instance_id = comp_info->instance_id; + tp->fr[tp->input_file_index].pipeline_id = ctx->pipeline_id; + tp->input_file_index++; + } + tb_setup_widget_ipc_msg(comp_info); + } + + return 0; +} + +int tb_new_pga(struct testbench_prm *tp) +{ + struct tplg_context *ctx = &tp->tplg; + struct tplg_comp_info *comp_info = ctx->current_comp_info; + struct ipc4_peak_volume_config volume; + struct snd_soc_tplg_ctl_hdr *tplg_ctl; + int ret; + + comp_info->ipc_size = + sizeof(struct ipc4_peak_volume_config) + sizeof(struct ipc4_base_module_cfg); + comp_info->ipc_payload = calloc(comp_info->ipc_size, 1); + if (!comp_info->ipc_payload) + return -ENOMEM; + + /* FIXME: move this to when the widget is actually set up */ + comp_info->instance_id = tp->instance_ids[SND_SOC_TPLG_DAPM_PGA]++; + comp_info->module_id = 0x6; + + tplg_ctl = calloc(ctx->hdr->payload_size, 1); + if (!tplg_ctl) { + ret = -ENOMEM; + goto out; + } + + ret = tplg_new_pga(ctx, &volume, sizeof(struct ipc4_peak_volume_config), + tplg_ctl, ctx->hdr->payload_size); + if (ret < 0) { + fprintf(stderr, "error: failed to create PGA\n"); + goto out; + } + + /* copy volume data to ipc_payload */ + memcpy(comp_info->ipc_payload + sizeof(struct ipc4_base_module_cfg), + &volume, sizeof(struct ipc4_peak_volume_config)); + + /* skip kcontrols for now */ + ret = tplg_create_controls(ctx, ctx->widget->num_kcontrols, tplg_ctl, + ctx->hdr->payload_size, &volume); + if (ret < 0) { + fprintf(stderr, "error: loading controls\n"); + goto out; + } + + tb_setup_widget_ipc_msg(comp_info); + free(tplg_ctl); + return ret; + +out: + free(tplg_ctl); + free(comp_info->ipc_payload); + return ret; +} + +int tb_new_process(struct testbench_prm *tp) +{ + struct tplg_context *ctx = &tp->tplg; + struct tplg_comp_info *comp_info = ctx->current_comp_info; + struct snd_soc_tplg_ctl_hdr *tplg_ctl; + int ret; + + ret = tplg_parse_widget_audio_formats(ctx); + if (ret < 0) + return ret; + + tplg_ctl = calloc(ctx->hdr->payload_size, 1); + if (!tplg_ctl) + return -ENOMEM; + + /* only base config supported for now. extn support will be added later */ + comp_info->ipc_size = sizeof(struct ipc4_base_module_cfg); + comp_info->ipc_payload = calloc(comp_info->ipc_size, 1); + if (!comp_info->ipc_payload) { + ret = ENOMEM; + goto out; + } + + /* FIXME: move this to when the widget is actually set up */ + comp_info->instance_id = tp->instance_ids[SND_SOC_TPLG_DAPM_EFFECT]++; + comp_info->module_id = 0x9e; /* dcblock */ + + /* skip kcontrols for now, set object to NULL */ + ret = tplg_create_controls(ctx, ctx->widget->num_kcontrols, tplg_ctl, + ctx->hdr->payload_size, NULL); + if (ret < 0) { + fprintf(stderr, "error: loading controls\n"); + goto out; + } + + tb_setup_widget_ipc_msg(comp_info); + + /* TODO: drop tplg_ctl to avoid memory leak. Need to store and handle this + * to support controls. + */ + free(tplg_ctl); + return 0; + +out: + free(tplg_ctl); + free(comp_info->ipc_payload); + return ret; +} + +static int tb_register_graph(struct testbench_prm *tp, int count) +{ + struct tplg_context *ctx = &tp->tplg; + int ret = 0; + int i; + + for (i = 0; i < count; i++) { + ret = tplg_parse_graph(ctx, &tp->widget_list, &tp->route_list); + if (ret < 0) + return ret; + } + + return ret; +} + +static int tb_parse_pcm(struct testbench_prm *tp, int count) +{ + struct tplg_context *ctx = &tp->tplg; + int ret, i; + + for (i = 0; i < count; i++) { + ret = tplg_parse_pcm(ctx, &tp->widget_list, &tp->pcm_list); + if (ret < 0) + return ret; + } + + return 0; +} + +/* + * create a list with all widget info + * containing mapping between component names and ids + * which will be used for setting up component connections + */ +static inline int tb_insert_comp(struct testbench_prm *tp) +{ + struct tplg_context *ctx = &tp->tplg; + struct tplg_comp_info *comp_info; + int comp_id = ctx->comp_id; + int ret; + + if (ctx->widget->id == SND_SOC_TPLG_DAPM_SCHEDULER) + return 0; + + comp_info = calloc(sizeof(struct tplg_comp_info), 1); + if (!comp_info) + return -ENOMEM; + + comp_info->name = strdup(ctx->widget->name); + if (!comp_info->name) { + ret = -ENOMEM; + goto err; + } + + comp_info->stream_name = strdup(ctx->widget->sname); + if (!comp_info->stream_name) { + ret = -ENOMEM; + goto err; + } + + comp_info->id = comp_id; + comp_info->type = ctx->widget->id; + comp_info->pipeline_id = ctx->pipeline_id; + ctx->current_comp_info = comp_info; + + ret = tb_parse_ipc4_comp_tokens(tp, &comp_info->basecfg); + if (ret < 0) + goto err; + + list_item_append(&comp_info->item, &tp->widget_list); + + printf("debug: loading comp_id %d: widget %s type %d size %d at offset %ld is_pages %d\n", + comp_id, ctx->widget->name, ctx->widget->id, ctx->widget->size, + ctx->tplg_offset, comp_info->basecfg.is_pages); + + return 0; + +err: + free(comp_info->name); + free(comp_info); + return ret; +} + +/* load dapm widget */ +static int tb_load_widget(struct testbench_prm *tp) +{ + struct tplg_context *ctx = &tp->tplg; + int ret = 0; + + /* get next widget */ + ctx->widget = tplg_get_widget(ctx); + ctx->widget_size = ctx->widget->size; + + /* insert widget into mapping */ + ret = tb_insert_comp(tp); + if (ret < 0) { + fprintf(stderr, "tb_load_widget: invalid widget index\n"); + return ret; + } + + /* load widget based on type */ + switch (tp->tplg.widget->id) { + /* load pga widget */ + case SND_SOC_TPLG_DAPM_PGA: + if (tb_new_pga(tp) < 0) { + fprintf(stderr, "error: load pga\n"); + return -EINVAL; + } + break; + case SND_SOC_TPLG_DAPM_AIF_IN: + if (tb_new_aif_in_out(tp, SOF_IPC_STREAM_PLAYBACK) < 0) { + fprintf(stderr, "error: load AIF IN failed\n"); + return -EINVAL; + } + break; + case SND_SOC_TPLG_DAPM_AIF_OUT: + if (tb_new_aif_in_out(tp, SOF_IPC_STREAM_CAPTURE) < 0) { + fprintf(stderr, "error: load AIF OUT failed\n"); + return -EINVAL; + } + break; + case SND_SOC_TPLG_DAPM_DAI_IN: + if (tb_new_dai_in_out(tp, SOF_IPC_STREAM_PLAYBACK) < 0) { + fprintf(stderr, "error: load filewrite\n"); + return -EINVAL; + } + break; + case SND_SOC_TPLG_DAPM_DAI_OUT: + if (tb_new_dai_in_out(tp, SOF_IPC_STREAM_CAPTURE) < 0) { + fprintf(stderr, "error: load filewrite\n"); + return -EINVAL; + } + break; + case SND_SOC_TPLG_DAPM_BUFFER: + if (tb_new_buffer(tp) < 0) { + fprintf(stderr, "error: load buffer\n"); + return -EINVAL; + } + break; + + case SND_SOC_TPLG_DAPM_SCHEDULER: + if (tb_new_pipeline(tp) < 0) { + fprintf(stderr, "error: load pipeline\n"); + return -EINVAL; + } + break; + + case SND_SOC_TPLG_DAPM_SRC: + if (tb_new_src(tp) < 0) { + fprintf(stderr, "error: load src\n"); + return -EINVAL; + } + break; + case SND_SOC_TPLG_DAPM_ASRC: + if (tb_new_asrc(tp) < 0) { + fprintf(stderr, "error: load src\n"); + return -EINVAL; + } + break; + case SND_SOC_TPLG_DAPM_MIXER: + if (tb_new_mixer(tp) < 0) { + fprintf(stderr, "error: load mixer\n"); + return -EINVAL; + } + break; + case SND_SOC_TPLG_DAPM_EFFECT: + if (tb_new_process(tp) < 0) { + fprintf(stderr, "error: load effect\n"); + return -EINVAL; + } + break; + + /* unsupported widgets */ + default: + printf("info: Widget %s id %d unsupported and skipped: size %d priv size %d\n", + ctx->widget->name, ctx->widget->id, + ctx->widget->size, ctx->widget->priv.size); + break; + } + + return 0; +} + +/* parse topology file and set up pipeline */ +int tb_parse_topology(struct testbench_prm *tp) + +{ + struct tplg_context *ctx = &tp->tplg; + struct snd_soc_tplg_hdr *hdr; + struct list_item *item; + int i; + int ret = 0; + FILE *file; + + ctx->ipc_major = 4; + + /* open topology file */ + file = fopen(ctx->tplg_file, "rb"); + if (!file) { + fprintf(stderr, "error: can't open topology %s : %s\n", ctx->tplg_file, + strerror(errno)); + return -errno; + } + + /* file size */ + if (fseek(file, 0, SEEK_END)) { + fprintf(stderr, "error: can't seek to end of topology: %s\n", + strerror(errno)); + fclose(file); + return -errno; + } + ctx->tplg_size = ftell(file); + if (fseek(file, 0, SEEK_SET)) { + fprintf(stderr, "error: can't seek to beginning of topology: %s\n", + strerror(errno)); + fclose(file); + return -errno; + } + + /* load whole topology into memory */ + ctx->tplg_base = calloc(ctx->tplg_size, 1); + if (!ctx->tplg_base) { + fprintf(stderr, "error: can't alloc buffer for topology %zu bytes\n", + ctx->tplg_size); + fclose(file); + return -ENOMEM; + } + ret = fread(ctx->tplg_base, ctx->tplg_size, 1, file); + if (ret != 1) { + fprintf(stderr, "error: can't read topology: %s\n", strerror(errno)); + free(ctx->tplg_base); + fclose(file); + return -errno; + } + fclose(file); + + /* initialize widget, route, pipeline and pcm lists */ + list_init(&tp->widget_list); + list_init(&tp->route_list); + list_init(&tp->pcm_list); + list_init(&tp->pipeline_list); + + while (ctx->tplg_offset < ctx->tplg_size) { + /* read next topology header */ + hdr = tplg_get_hdr(ctx); + + tplg_debug("type: %x, size: 0x%x count: %d index: %d\n", + hdr->type, hdr->payload_size, hdr->count, hdr->index); + + ctx->hdr = hdr; + + /* parse header and load the next block based on type */ + switch (hdr->type) { + /* load dapm widget */ + case SND_SOC_TPLG_TYPE_DAPM_WIDGET: + + tplg_debug("number of DAPM widgets %d\n", hdr->count); + + /* update max pipeline_id */ + ctx->pipeline_id = hdr->index; + + for (i = 0; i < hdr->count; i++) { + ret = tb_load_widget(tp); + if (ret < 0) { + fprintf(stderr, "error: loading widget\n"); + goto out; + } + ctx->comp_id++; + } + break; + + /* set up component connections from pipeline graph */ + case SND_SOC_TPLG_TYPE_DAPM_GRAPH: + ret = tb_register_graph(tp, hdr->count); + if (ret < 0) { + fprintf(stderr, "error: pipeline graph\n"); + goto out; + } + break; + + case SND_SOC_TPLG_TYPE_PCM: + ret = tb_parse_pcm(tp, hdr->count); + if (ret < 0) { + fprintf(stderr, "error: parsing pcm\n"); + goto out; + } + break; + + default: + tplg_skip_hdr_payload(ctx); + break; + } + } + + /* assign pipeline to every widget in the widget list */ + list_for_item(item, &tp->widget_list) { + struct tplg_comp_info *comp_info = container_of(item, struct tplg_comp_info, item); + struct list_item *pipe_item; + + list_for_item(pipe_item, &tp->pipeline_list) { + struct tplg_pipeline_info *pipe_info; + + pipe_info = container_of(pipe_item, struct tplg_pipeline_info, item); + if (pipe_info->id == comp_info->pipeline_id) { + comp_info->pipe_info = pipe_info; + break; + } + } + + if (!comp_info->pipe_info) { + fprintf(stderr, "warning: failed assigning pipeline for %s\n", + comp_info->name); + } + } + + return 0; + +out: + /* free all data */ + free(ctx->tplg_base); + return ret; +} + +/* + * To run + */ + +int tb_set_running_state(struct testbench_prm *tp) +{ + int ret; + + ret = tb_pipelines_set_state(tp, SOF_IPC4_PIPELINE_STATE_PAUSED, SOF_IPC_STREAM_PLAYBACK); + if (ret) { + fprintf(stderr, "error: failed to set state to paused\n"); + return ret; + } + + ret = tb_pipelines_set_state(tp, SOF_IPC4_PIPELINE_STATE_PAUSED, SOF_IPC_STREAM_CAPTURE); + if (ret) { + fprintf(stderr, "error: failed to set state to paused\n"); + return ret; + } + + fprintf(stdout, "pipelines are set to paused state\n"); + ret = tb_pipelines_set_state(tp, SOF_IPC4_PIPELINE_STATE_RUNNING, SOF_IPC_STREAM_PLAYBACK); + if (ret) { + fprintf(stderr, "error: failed to set state to running\n"); + return ret; + } + + ret = tb_pipelines_set_state(tp, SOF_IPC4_PIPELINE_STATE_RUNNING, SOF_IPC_STREAM_CAPTURE); + if (ret) + fprintf(stderr, "error: failed to set state to running\n"); + + fprintf(stdout, "pipelines are set to running state\n"); + return ret; +} + +/* + * To stop + */ + +int tb_set_reset_state(struct testbench_prm *tp) +{ + int ret; + + ret = tb_pipelines_set_state(tp, SOF_IPC4_PIPELINE_STATE_PAUSED, SOF_IPC_STREAM_PLAYBACK); + if (ret) { + fprintf(stderr, "error: failed to set state to paused\n"); + return ret; + } + + tb_schedule_pipeline_check_state(tp); + + ret = tb_pipelines_set_state(tp, SOF_IPC4_PIPELINE_STATE_RESET, SOF_IPC_STREAM_PLAYBACK); + if (ret) { + fprintf(stderr, "error: failed to set state to reset\n"); + return ret; + } + + tb_schedule_pipeline_check_state(tp); + + ret = tb_pipelines_set_state(tp, SOF_IPC4_PIPELINE_STATE_PAUSED, SOF_IPC_STREAM_CAPTURE); + if (ret) { + fprintf(stderr, "error: failed to set state to paused\n"); + return ret; + } + + tb_schedule_pipeline_check_state(tp); + + ret = tb_pipelines_set_state(tp, SOF_IPC4_PIPELINE_STATE_RESET, SOF_IPC_STREAM_CAPTURE); + if (ret) + fprintf(stderr, "error: failed to set state to reset\n"); + + tb_schedule_pipeline_check_state(tp); + return ret; +} + +int tb_delete_pipeline(struct testbench_prm *tp, struct tplg_pipeline_info *pipe_info) +{ + struct ipc4_pipeline_delete msg = {{0}}; + struct ipc4_message_reply reply; + int ret; + + msg.primary.r.type = SOF_IPC4_GLB_DELETE_PIPELINE; + msg.primary.r.msg_tgt = SOF_IPC4_MESSAGE_TARGET_FW_GEN_MSG; + msg.primary.r.rsp = SOF_IPC4_MESSAGE_DIR_MSG_REQUEST; + msg.primary.r.instance_id = pipe_info->instance_id; + + ret = tb_mq_cmd_tx_rx(&tp->ipc_tx, &tp->ipc_rx, &msg, sizeof(msg), &reply, sizeof(reply)); + if (ret < 0) { + fprintf(stderr, "error: can't delete pipeline %s\n", pipe_info->name); + return ret; + } + + if (reply.primary.r.status != IPC4_SUCCESS) { + fprintf(stderr, "pipeline %s instance ID %d delete failed with status %d\n", + pipe_info->name, pipe_info->instance_id, reply.primary.r.status); + return -EINVAL; + } + + tplg_debug("pipeline %s instance_id %d freed\n", pipe_info->name, + pipe_info->instance_id); + + return 0; +} + +int tb_free_route(struct testbench_prm *tp, struct tplg_route_info *route_info) +{ + struct tplg_comp_info *src_comp_info = route_info->source; + struct tplg_comp_info *sink_comp_info = route_info->sink; + struct ipc4_module_bind_unbind bu = {{0}}; + struct ipc4_message_reply reply; + int ret; + + /* only unbind when widgets belong to separate pipelines */ + if (src_comp_info->pipeline_id == sink_comp_info->pipeline_id) + return 0; + + bu.primary.r.module_id = src_comp_info->module_id; + bu.primary.r.instance_id = src_comp_info->instance_id; + bu.primary.r.type = SOF_IPC4_MOD_UNBIND; + bu.primary.r.msg_tgt = SOF_IPC4_MESSAGE_TARGET_MODULE_MSG; + bu.primary.r.rsp = SOF_IPC4_MESSAGE_DIR_MSG_REQUEST; + + bu.extension.r.dst_module_id = sink_comp_info->module_id; + bu.extension.r.dst_instance_id = sink_comp_info->instance_id; + + /* FIXME: assign queue ID for components with multiple inputs/outputs */ + bu.extension.r.dst_queue = 0; + bu.extension.r.src_queue = 0; + + ret = tb_mq_cmd_tx_rx(&tp->ipc_tx, &tp->ipc_rx, &bu, sizeof(bu), &reply, sizeof(reply)); + if (ret < 0) { + fprintf(stderr, "error: can't set up route %s -> %s\n", src_comp_info->name, + sink_comp_info->name); + return ret; + } + + if (reply.primary.r.status != IPC4_SUCCESS) { + fprintf(stderr, "route %s -> %s ID set up failed with status %d\n", + src_comp_info->name, sink_comp_info->name, reply.primary.r.status); + return -EINVAL; + } + + tplg_debug("route %s -> %s freed\n", src_comp_info->name, sink_comp_info->name); + + return 0; +} + +#endif /* CONFIG_IPC_MAJOR_4 */ diff --git a/uuid-registry.txt b/uuid-registry.txt index b597e27dcc0e..bbf92ecd2ec2 100644 --- a/uuid-registry.txt +++ b/uuid-registry.txt @@ -145,6 +145,7 @@ c1c5326d-8390-46b4-aa4795c3beca6550 src e61bb28d-149a-4c1f-b70946823ef5f5ae src4 33441051-44cd-466a-83a3178478708aea src_lite eb0bd14b-7d5e-4dfa-bbe27762adb279f0 swaudiodai +37c196ae-3532-4282-8a78dd9d50cc7123 testbench dd511749-d9fa-455c-b3a713585693f1af tdfb 04e3f894-2c5c-4f2e-8dc1694eeaab53fa tone 42f8060c-832f-4dbf-b24751e961997b34 up_down_mixer From 088893e8bf081dc84bf5f9c6ea4328a56ee13516 Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Thu, 19 Sep 2024 15:55:19 +0300 Subject: [PATCH 17/18] Tools: Testbench: Rename common_test C source files to utils The utils is a better description of the purpose of the functions in these files. There's no change to functionality. Signed-off-by: Seppo Ingalsuo --- tools/testbench/CMakeLists.txt | 6 +++--- tools/testbench/file.c | 2 +- tools/testbench/include/testbench/topology_ipc4.h | 2 +- .../testbench/include/testbench/{common_test.h => utils.h} | 6 +++--- tools/testbench/testbench.c | 2 +- tools/testbench/topology_ipc3.c | 2 +- tools/testbench/topology_ipc4.c | 2 +- tools/testbench/{common_test.c => utils.c} | 2 +- tools/testbench/{common_test_ipc3.c => utils_ipc3.c} | 2 +- tools/testbench/{common_test_ipc4.c => utils_ipc4.c} | 2 +- 10 files changed, 14 insertions(+), 14 deletions(-) rename tools/testbench/include/testbench/{common_test.h => utils.h} (97%) rename tools/testbench/{common_test.c => utils.c} (99%) rename tools/testbench/{common_test_ipc3.c => utils_ipc3.c} (99%) rename tools/testbench/{common_test_ipc4.c => utils_ipc4.c} (99%) diff --git a/tools/testbench/CMakeLists.txt b/tools/testbench/CMakeLists.txt index 179997981d94..f71e5635c8b2 100644 --- a/tools/testbench/CMakeLists.txt +++ b/tools/testbench/CMakeLists.txt @@ -12,9 +12,9 @@ set(default_asoc_h "/usr/include/alsa/sound/uapi/asoc.h") add_executable(testbench testbench.c file.c - common_test.c - common_test_ipc3.c - common_test_ipc4.c + utils.c + utils_ipc3.c + utils_ipc4.c topology_ipc3.c topology_ipc4.c ) diff --git a/tools/testbench/file.c b/tools/testbench/file.c index 71934a8669e8..21243f8629b0 100644 --- a/tools/testbench/file.c +++ b/tools/testbench/file.c @@ -22,7 +22,7 @@ #include #include #include -#include "testbench/common_test.h" +#include "testbench/utils.h" #include "testbench/file.h" #include "testbench/file_ipc4.h" #include "../../src/audio/copier/copier.h" diff --git a/tools/testbench/include/testbench/topology_ipc4.h b/tools/testbench/include/testbench/topology_ipc4.h index c9369862d402..66f4a10b00ef 100644 --- a/tools/testbench/include/testbench/topology_ipc4.h +++ b/tools/testbench/include/testbench/topology_ipc4.h @@ -7,7 +7,7 @@ #define _TESTBENCH_TOPOLOGY_IPC4_H #include -#include "testbench/common_test.h" +#include "testbench/utils.h" #define TB_IPC4_MAX_TPLG_OBJECT_SIZE 4096 #define TB_IPC4_MAX_MSG_SIZE 384 diff --git a/tools/testbench/include/testbench/common_test.h b/tools/testbench/include/testbench/utils.h similarity index 97% rename from tools/testbench/include/testbench/common_test.h rename to tools/testbench/include/testbench/utils.h index 801d9981a3b9..af299d67a21a 100644 --- a/tools/testbench/include/testbench/common_test.h +++ b/tools/testbench/include/testbench/utils.h @@ -3,8 +3,8 @@ * Copyright(c) 2018 Intel Corporation. All rights reserved. */ -#ifndef _TESTBENCH_COMMON_TEST_H -#define _TESTBENCH_COMMON_TEST_H +#ifndef _TESTBENCH_UTILS_H +#define _TESTBENCH_UTILS_H #include #include @@ -140,4 +140,4 @@ void tb_getcycles(uint64_t *cycles); void tb_gettime(struct timespec *td); void tb_show_file_stats(struct testbench_prm *tp, int pipeline_id); -#endif /* _TESTBENCH_COMMON_TEST_H */ +#endif /* _TESTBENCH_UTILS_H */ diff --git a/tools/testbench/testbench.c b/tools/testbench/testbench.c index 16d2fa771c83..7b7937921f33 100644 --- a/tools/testbench/testbench.c +++ b/tools/testbench/testbench.c @@ -13,7 +13,7 @@ #include "testbench/trace.h" #include "testbench/file.h" -#include "testbench/common_test.h" +#include "testbench/utils.h" #include #include diff --git a/tools/testbench/topology_ipc3.c b/tools/testbench/topology_ipc3.c index b61336292e14..a0082a370613 100644 --- a/tools/testbench/topology_ipc3.c +++ b/tools/testbench/topology_ipc3.c @@ -18,7 +18,7 @@ #include #include -#include "testbench/common_test.h" +#include "testbench/utils.h" #include "testbench/file.h" #include diff --git a/tools/testbench/topology_ipc4.c b/tools/testbench/topology_ipc4.c index 9020b5b03634..8fd35d6d57db 100644 --- a/tools/testbench/topology_ipc4.c +++ b/tools/testbench/topology_ipc4.c @@ -14,7 +14,7 @@ #include #include -#include "testbench/common_test.h" +#include "testbench/utils.h" #include "testbench/topology_ipc4.h" #include "testbench/file.h" #include "testbench/file_ipc4.h" diff --git a/tools/testbench/common_test.c b/tools/testbench/utils.c similarity index 99% rename from tools/testbench/common_test.c rename to tools/testbench/utils.c index 6198663949f0..438cc8badbca 100644 --- a/tools/testbench/common_test.c +++ b/tools/testbench/utils.c @@ -13,7 +13,7 @@ #include #include -#include "testbench/common_test.h" +#include "testbench/utils.h" #include "testbench/trace.h" #include "testbench/file.h" diff --git a/tools/testbench/common_test_ipc3.c b/tools/testbench/utils_ipc3.c similarity index 99% rename from tools/testbench/common_test_ipc3.c rename to tools/testbench/utils_ipc3.c index ba3b77307b6e..b6e259da37be 100644 --- a/tools/testbench/common_test_ipc3.c +++ b/tools/testbench/utils_ipc3.c @@ -14,7 +14,7 @@ #include #include -#include "testbench/common_test.h" +#include "testbench/utils.h" #include "testbench/file.h" /* testbench helper functions for pipeline setup and trigger */ diff --git a/tools/testbench/common_test_ipc4.c b/tools/testbench/utils_ipc4.c similarity index 99% rename from tools/testbench/common_test_ipc4.c rename to tools/testbench/utils_ipc4.c index 8ede31d77718..a86a8e02c585 100644 --- a/tools/testbench/common_test_ipc4.c +++ b/tools/testbench/utils_ipc4.c @@ -17,7 +17,7 @@ #include #include -#include "testbench/common_test.h" +#include "testbench/utils.h" #include "testbench/file.h" #include "testbench/topology_ipc4.h" From 2a0284fb2b941ee0369dd68ab40e662b2609fe01 Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Thu, 19 Sep 2024 18:30:55 +0300 Subject: [PATCH 18/18] Tools: Testbench: Add README.md text This text add helps with various usages for testbench. Signed-off-by: Seppo Ingalsuo --- tools/testbench/README.md | 154 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 tools/testbench/README.md diff --git a/tools/testbench/README.md b/tools/testbench/README.md new file mode 100644 index 000000000000..f4fbd3edefe5 --- /dev/null +++ b/tools/testbench/README.md @@ -0,0 +1,154 @@ +# SOF testbench + +### Features + + * Simulate IPC3 and IPC4 SOF versions with run of set of processing + components based on desired topology. + * Replaces host and dai components with file I/O with raw binary + S16_LE/S24_LE/S32_LE or text format files for audio waveforms. + * Much faster than real-time execution in native build, e.g. x86 on + Linux for efficient validation usage. + * With xtensa DSP build offers cycles accurate simulated environment + execution. And also with options for simulation speed vs. model + accuracy. + * Allows easy use of conventional debugger, profiler, leak and memory check + tools usage for DSP firmware code. + +### Quick how-to + +The simplest way to build and execute testbench is with supplied +scripts. It executes a simple chirp waveform test for a number of +processing components. Corrupted audio or failure in execution +results to fail of test. + +The commands "build-tools.sh -t" and "host-testbench.sh" are currently +valid only for default IPC3 build of testbench. + +``` +cd $SOF_WORKSPACE/sof +scripts/build-tools.sh -t +scripts/rebuild-testbench.sh +scripts/host-testbench.sh +``` + +### Manual run of IPC3 testbench + +As an example, process a wav file and listen it. The example +processing component is DC block. The wav file is first converted with +sox to raw 32 bit 48 kHz stereo format, then processed by testbench, +converted back to wav. + +``` +cd $SOF_WORKSPACE/sof +sox --encoding signed-integer /usr/share/sounds/alsa/Front_Left.wav -L -r 48000 -c 2 -b 32 in.raw +tools/testbench/build_testbench/install/bin/testbench -r 48000 -R 48000 -c 2 -n 2 -b S32_LE \ + -t tools/build_tools/test/topology/test-playback-ssp5-mclk-0-I2S-dcblock-s32le-s32le-48k-24576k-codec.tplg \ + -i in.raw -o out.raw +sox --encoding signed-integer -L -r 48000 -c 2 -b 32 in.raw out.wav +aplay out.wav +``` + +The testbench binary can be debugged and profiled with native +executable tools like gdb, ddd (GUI for gdb), gprof, and valgrind. + +### Profiling of testbench run on xtensa + +Fist, the testbench is build in this example for Intel MTL +platform. This needs access to Cadence Xplorer toolchain with the core +confiration for the platform. + +``` +cd $SOF_WORKSPACE/sof +export XTENSA_TOOLS_ROOT=~/xtensa/XtDevTools +export ZEPHYR_TOOLCHAIN_VARIANT=xt-clang +scripts/rebuild-testbench.sh -p mtl +source tools/testbench/build_xt_testbench/xtrun_env.sh +``` + +Next the testbench is run with xt-run simulator. The example component +is dynamic range processing (DRC). For proling data generate use +option --profile. Note that this is a lot slower than native run. An +1s extract of a pink noise file is used as example. + +``` +sox --encoding signed-integer /usr/share/sounds/alsa/Noise.wav -L -r 48000 -c 2 -b 32 in.raw trim 0.0 1.0 +$XTENSA_PATH/xt-run --profile=profile.out tools/testbench/build_xt_testbench/testbench \ + -q -r 48000 -R 48000 -c 2 -n 2 -b S32_LE \ + -t tools/build_tools/test/topology/test-playback-ssp5-mclk-0-I2S-drc-s32le-s32le-48k-24576k-codec.tplg \ + -i in.raw -o out.raw 2> trace.txt +``` + +Then convert the profiler data to readable format and check it. + +Note: Current version of testbench does not have functional quiet mode +"-q" switch to suppress trace. Due all debug traces print +out. Majority of performance is spent in printing. + +``` +$XTENSA_PATH/xt-gprof tools/testbench/build_xt_testbench/testbench profile.out > example_profile.txt +less example_profile.txt +``` + +### Perform audio processing quality checks + +This step needs Matlab or Octave tool with signal processing +package. A number of tests is performed for IIR equalizer component as +example. The script opens plot windows and outputs a text report in +the end. A simple pass/fail criteria is used to report a verdict of +the test run. + +``` +cd $SOF_WORKSPACE/sof/tools/test/audio +octave -q --eval "pkg load signal io; [n_fail]=process_test('eq-iir', 32, 32, 48000, 1, 1); input('Press ENTER'); exit(n_fail)" +``` + +See from interactive Octave shell command "help process_test" the +explanation for the test run parameters. + +### Manual run of IPC4 testbench + +Apply this patch to SOF and rebuild it. + +``` +diff --git a/src/arch/host/configs/library_defconfig b/src/arch/host/configs/library_defconfig +index d150690aa..6d167dfba 100644 +--- a/src/arch/host/configs/library_defconfig ++++ b/src/arch/host/configs/library_defconfig +@@ -21,8 +21,8 @@ CONFIG_COMP_VOLUME=y + CONFIG_COMP_VOLUME_LINEAR_RAMP=y + CONFIG_COMP_VOLUME_WINDOWS_FADE=y + CONFIG_DEBUG_MEMORY_USAGE_SCAN=n +-CONFIG_IPC_MAJOR_3=y +-CONFIG_IPC_MAJOR_4=n ++CONFIG_IPC_MAJOR_3=n ++CONFIG_IPC_MAJOR_4=y + CONFIG_LIBRARY=y + CONFIG_LIBRARY_STATIC=y + CONFIG_MATH_IIR_DF2T=y +``` + +Then process a wav file and listen. The example processing component +is DC blocker. The wav file is first converted with sox to raw 32 bit +48 kHz stereo format, then processed by testbench, converted back to +wav. + +``` +cd $SOF_WORKSPACE/sof +sox --encoding signed-integer /usr/share/sounds/alsa/Front_Center.wav -L -r 48000 -c 2 -b 32 in.raw +tools/testbench/build_testbench/install/bin/testbench -r 48000 -R 48000 -c 2 -n 2 -b S32_LE -p 1,2 \ + -t tools/build_tools/topology/topology2/development/sof-hda-benchmark-dcblock32.tplg \ + -i in.raw -o out.raw +sox --encoding signed-integer -L -r 48000 -c 2 -b 32 in.raw out.wav +aplay out.wav +``` + +The difference in command line is the use of other topology for IPC4 +and use of -p command line option to select pipelines 1 and 2 from +this topology for scheduling and connecting to input and output files. +In this topology the host playback pipeline is 1 and the dai playback +side pipeline is 2. Capture side pipelines would be 3 and 4. And +running both playback and capture would need more input and output +files to be added to -i and -o as comma separated. The more advanced +use cases simulations with IPC4 are still under work. + +The debugging and profiling of IPC4 testench is similar as with IPC3.