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/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; 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); } 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; 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 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); 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; diff --git a/src/ipc/ipc4/handler.c b/src/ipc/ipc4/handler.c index ff3ad559a0a6..3e9b5d1b4472 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) { @@ -1603,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) diff --git a/src/ipc/ipc4/helper.c b/src/ipc/ipc4/helper.c index c7798861a518..0a8549c65b0f 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) { @@ -982,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) 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; diff --git a/tools/testbench/CMakeLists.txt b/tools/testbench/CMakeLists.txt index 8e95a60d6a14..f71e5635c8b2 100644 --- a/tools/testbench/CMakeLists.txt +++ b/tools/testbench/CMakeLists.txt @@ -11,9 +11,12 @@ set(default_asoc_h "/usr/include/alsa/sound/uapi/asoc.h") add_executable(testbench testbench.c - common_test.c file.c - topology.c + utils.c + utils_ipc3.c + utils_ipc4.c + topology_ipc3.c + topology_ipc4.c ) sof_append_relative_path_definitions(testbench) 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. diff --git a/tools/testbench/file.c b/tools/testbench/file.c index bd3584155005..21243f8629b0 100644 --- a/tools/testbench/file.c +++ b/tools/testbench/file.c @@ -22,8 +22,11 @@ #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" + 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,89 +571,115 @@ 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; - debug_print("file_init()\n"); +#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; /* 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); /* 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 */ @@ -632,31 +689,34 @@ 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); - debug_print("file_free()"); + tb_debug_print("file_free()"); if (cd->fs.mode == FILE_READ) fclose(cd->fs.rfh); 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; } @@ -669,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) @@ -710,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; - debug_print("file_process(): reached EOF"); - schedule_task_cancel(mod->dev->pipeline->pipe_task); + cd->fs.reached_eof = true; + tb_debug_print("file_process(): reached EOF"); + } + + 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); @@ -727,9 +796,9 @@ 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)); - debug_print("file_prepare()"); + tb_debug_print("file_prepare()"); /* file component sink/source buffer period count */ cd->max_frames = dev->frames; @@ -769,15 +838,16 @@ static int file_prepare(struct processing_module *mod, static int file_reset(struct processing_module *mod) { - 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) { - debug_print("asrc_trigger()"); - + tb_debug_print("file_trigger()"); return comp_set_state(dev, cmd); } @@ -785,9 +855,9 @@ 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)); - 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 deleted file mode 100644 index 4960c13e2d02..000000000000 --- a/tools/testbench/include/testbench/common_test.h +++ /dev/null @@ -1,108 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * - * Copyright(c) 2018 Intel Corporation. All rights reserved. - */ - -#ifndef _COMMON_TEST_H -#define _COMMON_TEST_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#define DEBUG_MSG_LEN 1024 -#define MAX_LIB_NAME_LEN 1024 - -#define MAX_INPUT_FILE_NUM 16 -#define MAX_OUTPUT_FILE_NUM 16 - -/* number of widgets types supported in testbench */ -#define NUM_WIDGETS_SUPPORTED 16 - -struct tplg_context; - -/* - * Global testbench data. - * - * TODO: some items are topology and pipeline specific and need moved out - * into per pipeline data and per topology data structures. - */ -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 */ - 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 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; - - struct tplg_comp_info *info; - int info_index; - int info_elems; - - /* - * input and output sample rate parameters - * By default, these are calculated from pipeline frames_per_sched - * and period but they can also be overridden via input arguments - * to the testbench. - */ - uint32_t fs_in; - uint32_t fs_out; - uint32_t channels_in; - uint32_t channels_out; - enum sof_ipc_frame frame_fmt; -}; - -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_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); - -void tb_getcycles(uint64_t *cycles); - -#endif 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..66f4a10b00ef --- /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/utils.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/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/include/testbench/utils.h b/tools/testbench/include/testbench/utils.h new file mode 100644 index 000000000000..af299d67a21a --- /dev/null +++ b/tools/testbench/include/testbench/utils.h @@ -0,0 +1,143 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2018 Intel Corporation. All rights reserved. + */ + +#ifndef _TESTBENCH_UTILS_H +#define _TESTBENCH_UTILS_H + +#include +#include +#include + + +#include + +#define TB_DEBUG_MSG_LEN 1024 +#define TB_MAX_LIB_NAME_LEN 1024 + +#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; +}; + +#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. + * + * TODO: some items are topology and pipeline specific and need moved out + * into per pipeline data and per topology data structures. + */ +struct testbench_prm { + long long total_cycles; + 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 */ + int pipeline_num; + int copy_iterations; + bool copy_check; + bool quiet; + int dynamic_pipeline_iterations; + int tick_period_us; + int pipeline_duration_ms; + char *pipeline_string; + int output_file_index; + int input_file_index; + + struct tplg_comp_info *info; + int info_index; + int info_elems; + + /* + * input and output sample rate parameters + * By default, these are calculated from pipeline frames_per_sched + * and period but they can also be overridden via input arguments + * to the testbench. + */ + uint32_t fs_in; + uint32_t fs_out; + uint32_t channels_in; + uint32_t channels_out; + enum sof_ipc_frame frame_fmt; + + /* 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; + +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_stop(struct ipc *ipc, struct pipeline *p); +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_UTILS_H */ diff --git a/tools/testbench/testbench.c b/tools/testbench/testbench.c index 0f464d577f1f..7b7937921f33 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/utils.h" + +#include #include -#include #include +#include +#include #define TESTBENCH_NCH 2 @@ -33,7 +35,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 +43,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 +65,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 +73,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 +92,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 +100,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; } @@ -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) -{ - 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) +static void test_pipeline_stats(struct testbench_prm *tp, long long delta_t) { - 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 < MAX_OUTPUT_FILE_NUM; i++) - tp.output_file[i] = NULL; - - for (i = 0; i < 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, 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.c b/tools/testbench/topology_ipc3.c similarity index 97% rename from tools/testbench/topology.c rename to tools/testbench/topology_ipc3.c index d6163e19d6f9..a0082a370613 100644 --- a/tools/testbench/topology.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/utils.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 */ diff --git a/tools/testbench/topology_ipc4.c b/tools/testbench/topology_ipc4.c new file mode 100644 index 000000000000..8fd35d6d57db --- /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/utils.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/tools/testbench/utils.c b/tools/testbench/utils.c new file mode 100644 index 000000000000..438cc8badbca --- /dev/null +++ b/tools/testbench/utils.c @@ -0,0 +1,254 @@ +// 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/utils.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 = get_file_comp_data(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 || tp->fr[i].state->copy_timeout) + 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->copy_timeout || + 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 = 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); + } + + 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 = 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); + } +} + +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.c b/tools/testbench/utils_ipc3.c similarity index 52% rename from tools/testbench/common_test.c rename to tools/testbench/utils_ipc3.c index f4481b38af02..b6e259da37be 100644 --- a/tools/testbench/common_test.c +++ b/tools/testbench/utils_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/utils.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,12 +150,11 @@ 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}}; - char message[DEBUG_MSG_LEN]; + char message[TB_DEBUG_MSG_LEN]; int fs_period; int period; int ret = 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/utils_ipc4.c b/tools/testbench/utils_ipc4.c new file mode 100644 index 000000000000..a86a8e02c585 --- /dev/null +++ b/tools/testbench/utils_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/utils.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/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