From e84305a3e6d53e1985ca3b10dcde9c65e83260f0 Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Tue, 17 Sep 2024 19:59:51 +0300 Subject: [PATCH] Tools: Testbench: Add IPC4 support This patch adds topology parsing and common functions versions for IPC4. Due to dai_get_init_delay_ms() implementation in IPC4 build the file component is changed internally to copier to provide the DAI data struct. The change is common for both IPC3 and IPC4 though copier is not usually used with IPC3 systems. Since it works the same solution is used. The file state retrieve is changed because the file component data is placed deeper into the structures. Due to IPC4 scheduling of pipelines the file component is added a timeout. A file component sets timeout status if there has been three copy operations with no data to process. The timeout and EOF are used to end cleanly the test run. The library_defconfig still has CONFIG_IPC_MAJOR_4=n. The add of build type select to scripts/rebuild-testbench.sh is further work. Also the IPC4 testbench in this state is not well usable with only one component supported as process component and without byte control set up algorithms. Test run with DC blocker is possible this way: tools/testbench/build_testbench/install/bin/testbench -r 48000 -R 48000 -c 2 -n 2 -b S32_LE -p 1,2 -t tools/build_tools/topology/topology2/development/ sof-hda-benchmark-dcblock32.tplg -i in.raw -o out.raw Also sof-hda-benchmark-gain32.tplg can be run. Signed-off-by: Seppo Ingalsuo --- src/arch/host/configs/library_defconfig | 1 + tools/testbench/CMakeLists.txt | 4 +- tools/testbench/common_test.c | 13 +- tools/testbench/common_test_ipc4.c | 593 ++++++++ tools/testbench/file.c | 132 +- .../testbench/include/testbench/common_test.h | 37 + tools/testbench/include/testbench/file.h | 18 + tools/testbench/include/testbench/file_ipc4.h | 26 + .../include/testbench/topology_ipc4.h | 38 + tools/testbench/topology_ipc4.c | 1304 +++++++++++++++++ uuid-registry.txt | 1 + 11 files changed, 2127 insertions(+), 40 deletions(-) create mode 100644 tools/testbench/common_test_ipc4.c create mode 100644 tools/testbench/include/testbench/file_ipc4.h create mode 100644 tools/testbench/include/testbench/topology_ipc4.h create mode 100644 tools/testbench/topology_ipc4.c diff --git a/src/arch/host/configs/library_defconfig b/src/arch/host/configs/library_defconfig index 81226d252c82..d150690aa881 100644 --- a/src/arch/host/configs/library_defconfig +++ b/src/arch/host/configs/library_defconfig @@ -22,6 +22,7 @@ CONFIG_COMP_VOLUME_LINEAR_RAMP=y CONFIG_COMP_VOLUME_WINDOWS_FADE=y CONFIG_DEBUG_MEMORY_USAGE_SCAN=n CONFIG_IPC_MAJOR_3=y +CONFIG_IPC_MAJOR_4=n CONFIG_LIBRARY=y CONFIG_LIBRARY_STATIC=y CONFIG_MATH_IIR_DF2T=y diff --git a/tools/testbench/CMakeLists.txt b/tools/testbench/CMakeLists.txt index daccb4ccaccd..179997981d94 100644 --- a/tools/testbench/CMakeLists.txt +++ b/tools/testbench/CMakeLists.txt @@ -11,10 +11,12 @@ set(default_asoc_h "/usr/include/alsa/sound/uapi/asoc.h") add_executable(testbench testbench.c + file.c common_test.c common_test_ipc3.c - file.c + common_test_ipc4.c topology_ipc3.c + topology_ipc4.c ) sof_append_relative_path_definitions(testbench) diff --git a/tools/testbench/common_test.c b/tools/testbench/common_test.c index 65443e366d1b..9b73b8f5b660 100644 --- a/tools/testbench/common_test.c +++ b/tools/testbench/common_test.c @@ -71,7 +71,7 @@ int tb_find_file_components(struct testbench_prm *tp) fprintf(stderr, "error: null module.\n"); return -EINVAL; } - fcd = module_get_private_data(mod); + fcd = get_file_comp_data(module_get_private_data(mod)); tp->fr[i].state = &fcd->fs; } @@ -98,7 +98,7 @@ int tb_find_file_components(struct testbench_prm *tp) return -EINVAL; } - fcd = module_get_private_data(mod); + fcd = get_file_comp_data(module_get_private_data(mod)); tp->fw[i].state = &fcd->fs; } @@ -113,7 +113,7 @@ static bool tb_is_file_component_at_eof(struct testbench_prm *tp) if (!tp->fr[i].state) continue; - if (tp->fr[i].state->reached_eof) + if (tp->fr[i].state->reached_eof || tp->fr[i].state->copy_timeout) return true; } @@ -121,7 +121,8 @@ static bool tb_is_file_component_at_eof(struct testbench_prm *tp) if (!tp->fw[i].state) continue; - if (tp->fw[i].state->reached_eof || tp->fw[i].state->write_failed) + if (tp->fw[i].state->reached_eof || tp->fw[i].state->copy_timeout || + tp->fw[i].state->write_failed) return true; } @@ -146,7 +147,7 @@ void tb_show_file_stats(struct testbench_prm *tp, int pipeline_id) dev = icd->cd; mod = comp_mod(dev); - fcd = module_get_private_data(mod); + fcd = get_file_comp_data(module_get_private_data(mod)); printf("file %s: id %d: type %d: samples %d copies %d\n", fcd->fs.fn, dev->ipc_config.id, dev->drv->type, fcd->fs.n, fcd->fs.copy_count); @@ -162,7 +163,7 @@ void tb_show_file_stats(struct testbench_prm *tp, int pipeline_id) dev = icd->cd; mod = comp_mod(dev); - fcd = module_get_private_data(mod); + fcd = get_file_comp_data(module_get_private_data(mod)); printf("file %s: id %d: type %d: samples %d copies %d\n", fcd->fs.fn, dev->ipc_config.id, dev->drv->type, fcd->fs.n, fcd->fs.copy_count); diff --git a/tools/testbench/common_test_ipc4.c b/tools/testbench/common_test_ipc4.c new file mode 100644 index 000000000000..28fd200b5b16 --- /dev/null +++ b/tools/testbench/common_test_ipc4.c @@ -0,0 +1,593 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2018-2024 Intel Corporation. All rights reserved. + +#if CONFIG_IPC_MAJOR_4 + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "testbench/common_test.h" +#include "testbench/file.h" +#include "testbench/topology_ipc4.h" + +#if defined __XCC__ +#include +#endif + +SOF_DEFINE_REG_UUID(testbench); +DECLARE_TR_CTX(testbench_tr, SOF_UUID(testbench_uuid), LOG_LEVEL_INFO); +LOG_MODULE_REGISTER(testbench, CONFIG_SOF_LOG_LEVEL); + +/* testbench helper functions for pipeline setup and trigger */ + +int tb_setup(struct sof *sof, struct testbench_prm *tp) +{ + struct ll_schedule_domain domain = {0}; + int bits; + int krate; + + domain.next_tick = tp->tick_period_us; + + /* init components */ + sys_comp_init(sof); + + /* Module adapter components */ + sys_comp_module_crossover_interface_init(); + sys_comp_module_dcblock_interface_init(); + sys_comp_module_demux_interface_init(); + sys_comp_module_drc_interface_init(); + sys_comp_module_eq_fir_interface_init(); + sys_comp_module_eq_iir_interface_init(); + sys_comp_module_file_interface_init(); + sys_comp_module_gain_interface_init(); + sys_comp_module_google_rtc_audio_processing_interface_init(); + sys_comp_module_igo_nr_interface_init(); + sys_comp_module_mfcc_interface_init(); + sys_comp_module_multiband_drc_interface_init(); + sys_comp_module_mux_interface_init(); + sys_comp_module_rtnr_interface_init(); + sys_comp_module_selector_interface_init(); + sys_comp_module_src_interface_init(); + sys_comp_module_asrc_interface_init(); + sys_comp_module_tdfb_interface_init(); + sys_comp_module_volume_interface_init(); + + /* other necessary initializations, todo: follow better SOF init */ + 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 used for what? determine from topology and enabled pipelines if need */ + 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++; + } + + return 0; +} + +static int tb_prepare_widgets(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 || + route_info->sink->type != SND_SOC_TPLG_DAPM_DAI_OUT) { + ret = tb_prepare_widgets(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_IN && + 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 */ + ret = tb_set_up_widget_ipc(tp, comp_info); + if (ret < 0) + return ret; + + return 0; +} + +static int tb_set_up_widgets(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 || + route_info->sink->type != SND_SOC_TPLG_DAPM_DAI_OUT) { + ret = tb_set_up_widgets(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_IN && + 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; + + // TODO tp->pcm_id is not defined? + 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 + + 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(tp, pcm_info, host, host); + if (ret < 0) + return ret; + + ret = tb_set_up_widgets(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(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 || + route_info->sink->type != SND_SOC_TPLG_DAPM_DAI_OUT) { + ret = tb_free_widgets(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_IN && + 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(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); + 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; + 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); + free(route_info); + } + + list_for_item_safe(item, _item, &tp->pipeline_list) { + pipe_info = container_of(item, struct tplg_pipeline_info, item); + free(pipe_info->name); + free(pipe_info); + } + + // TODO: Do here or earlier? + free(ctx->tplg_base); + tb_debug_print("freed all pipelines, widgets, routes and pcms\n"); +} + +#endif /* CONFIG_IPC_MAJOR_4 */ diff --git a/tools/testbench/file.c b/tools/testbench/file.c index 2ef9e86ab1c4..4782afbf38dc 100644 --- a/tools/testbench/file.c +++ b/tools/testbench/file.c @@ -24,6 +24,9 @@ #include #include "testbench/common_test.h" #include "testbench/file.h" +#include "testbench/file_ipc4.h" +#include "../../src/audio/copier/copier.h" + SOF_DEFINE_REG_UUID(file); DECLARE_TR_CTX(file_tr, SOF_UUID(file_uuid), LOG_LEVEL_INFO); @@ -524,9 +527,37 @@ static enum file_format get_file_format(char *filename) return FILE_RAW; } -static int file_init_set_dai_data(struct comp_dev *dev) +#if CONFIG_IPC_MAJOR_4 +/* Minimal support for IPC4 pipeline_comp_trigger()'s dai_get_init_delay_ms() */ +static int file_init_set_dai_data(struct processing_module *mod) { struct dai_data *dd; + struct copier_data *ccd = module_get_private_data(mod); + + dd = rzalloc(SOF_MEM_ZONE_RUNTIME_SHARED, 0, SOF_MEM_CAPS_RAM, sizeof(*dd)); + if (!dd) + return -ENOMEM; + + /* Member dd->dai remains NULL. It's sufficient for dai_get_init_delay_ms(). + * In such case the functions returns zero delay. Testbench currently has + * no use for the feature. + */ + ccd->dd[0] = dd; + return 0; +} + +static void file_free_dai_data(struct processing_module *mod) +{ + struct copier_data *ccd = module_get_private_data(mod); + + free(ccd->dd[0]); +} +#else +/* Minimal support for IPC3 pipeline_comp_trigger()'s dai_get_init_delay_ms() */ +static int file_init_set_dai_data(struct processing_module *mod) +{ + struct dai_data *dd; + struct comp_dev *dev = mod->dev; dd = rzalloc(SOF_MEM_ZONE_RUNTIME_SHARED, 0, SOF_MEM_CAPS_RAM, sizeof(*dd)); if (!dd) @@ -540,30 +571,51 @@ static int file_init_set_dai_data(struct comp_dev *dev) return 0; } -static void file_free_dai_data(struct comp_dev *dev) +static void file_free_dai_data(struct processing_module *mod) { struct dai_data *dd; + struct comp_dev *dev = mod->dev; + dd = comp_get_drvdata(dev); free(dd); } +#endif static int file_init(struct processing_module *mod) { struct comp_dev *dev = mod->dev; struct module_data *mod_data = &mod->priv; - const struct ipc_comp_file *ipc_file = - (const struct ipc_comp_file *)mod_data->cfg.init_data; + struct copier_data *ccd; struct file_comp_data *cd; int ret; +#if CONFIG_IPC_MAJOR_4 + const struct ipc4_file_module_cfg *module_cfg = + (const struct ipc4_file_module_cfg *)mod_data->cfg.init_data; + + const struct ipc4_file_config *ipc_file = &module_cfg->config; +#else + const struct ipc_comp_file *ipc_file = + (const struct ipc_comp_file *)mod_data->cfg.init_data; +#endif + tb_debug_print("file_init()\n"); + ccd = rzalloc(SOF_MEM_ZONE_RUNTIME_SHARED, 0, SOF_MEM_CAPS_RAM, sizeof(*ccd)); + if (!ccd) + return -ENOMEM; + + mod_data->private = ccd; + + /* File component data is placed to copier's ipcgtw_data */ cd = rzalloc(SOF_MEM_ZONE_RUNTIME_SHARED, 0, SOF_MEM_CAPS_RAM, sizeof(*cd)); - if (!cd) + if (!cd) { + free(ccd); return -ENOMEM; + } - mod_data->private = cd; + file_set_comp_data(ccd, cd); /* default function for processing samples */ cd->file_func = file_default; @@ -576,53 +628,54 @@ static int file_init(struct processing_module *mod) /* set file comp mode */ cd->fs.mode = ipc_file->mode; - cd->rate = ipc_file->rate; cd->channels = ipc_file->channels; cd->frame_fmt = ipc_file->frame_fmt; dev->direction = ipc_file->direction; + dev->direction_set = true; /* open file handle(s) depending on mode */ switch (cd->fs.mode) { case FILE_READ: + cd->fs.rfh = fopen(cd->fs.fn, "r"); + if (!cd->fs.rfh) { + fprintf(stderr, "error: opening file %s for reading - %s\n", + cd->fs.fn, strerror(errno)); + goto error; + } + /* Change to DAI type is needed to avoid uninitialized hw params in * pipeline_params, A file host can be left as SOF_COMP_MODULE_ADAPTER */ if (dev->direction == SOF_IPC_STREAM_CAPTURE) { dev->ipc_config.type = SOF_COMP_DAI; - ret = file_init_set_dai_data(dev); + ret = file_init_set_dai_data(mod); if (ret) { fprintf(stderr, "error: failed set dai data.\n"); goto error; } } - - cd->fs.rfh = fopen(cd->fs.fn, "r"); - if (!cd->fs.rfh) { - fprintf(stderr, "error: opening file %s for reading - %s\n", + break; + case FILE_WRITE: + cd->fs.wfh = fopen(cd->fs.fn, "w+"); + if (!cd->fs.wfh) { + fprintf(stderr, "error: opening file %s for writing - %s\n", cd->fs.fn, strerror(errno)); goto error; } - break; - case FILE_WRITE: + /* Change to DAI type is needed to avoid uninitialized hw params in * pipeline_params, A file host can be left as SOF_COMP_MODULE_ADAPTER */ if (dev->direction == SOF_IPC_STREAM_PLAYBACK) { dev->ipc_config.type = SOF_COMP_DAI; - ret = file_init_set_dai_data(dev); + ret = file_init_set_dai_data(mod); if (ret) { fprintf(stderr, "error: failed set dai data.\n"); goto error; } } - cd->fs.wfh = fopen(cd->fs.fn, "w+"); - if (!cd->fs.wfh) { - fprintf(stderr, "error: opening file %s for writing - %s\n", - cd->fs.fn, strerror(errno)); - goto error; - } break; default: /* TODO: duplex mode */ @@ -632,20 +685,22 @@ static int file_init(struct processing_module *mod) cd->fs.reached_eof = false; cd->fs.write_failed = false; + cd->fs.copy_timeout = false; cd->fs.n = 0; cd->fs.copy_count = 0; cd->fs.cycles_count = 0; - return 0; error: free(cd); + free(ccd); return -EINVAL; } static int file_free(struct processing_module *mod) { - struct file_comp_data *cd = module_get_private_data(mod); + struct copier_data *ccd = module_get_private_data(mod); + struct file_comp_data *cd = get_file_comp_data(ccd); tb_debug_print("file_free()"); @@ -654,9 +709,10 @@ static int file_free(struct processing_module *mod) else fclose(cd->fs.wfh); + file_free_dai_data(mod); free(cd->fs.fn); free(cd); - file_free_dai_data(mod->dev); + free(ccd); return 0; } @@ -669,13 +725,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 +766,18 @@ static int file_process(struct processing_module *mod, cd->fs.copy_count++; if (cd->fs.reached_eof || (cd->max_copies && cd->fs.copy_count >= cd->max_copies)) { - cd->fs.reached_eof = 1; + cd->fs.reached_eof = true; tb_debug_print("file_process(): reached EOF"); - schedule_task_cancel(mod->dev->pipeline->pipe_task); + } + + if (samples) { + cd->copies_timeout_count = 0; + } else { + cd->copies_timeout_count++; + if (cd->copies_timeout_count == FILE_MAX_COPIES_TIMEOUT) { + tb_debug_print("file_process(): copies_timeout reached\n"); + cd->fs.copy_timeout = true; + } } tb_getcycles(&cycles1); @@ -727,7 +792,7 @@ static int file_prepare(struct processing_module *mod, struct audio_stream *stream; struct comp_buffer *buffer; struct comp_dev *dev = mod->dev; - struct file_comp_data *cd = module_get_private_data(mod); + struct file_comp_data *cd = get_file_comp_data(module_get_private_data(mod)); tb_debug_print("file_prepare()"); @@ -769,15 +834,16 @@ static int file_prepare(struct processing_module *mod, static int file_reset(struct processing_module *mod) { - tb_debug_print("file_reset()"); + struct file_comp_data *cd = module_get_private_data(mod); + tb_debug_print("file_reset()"); + cd->copies_timeout_count = 0; return 0; } static int file_trigger(struct comp_dev *dev, int cmd) { - tb_debug_print("asrc_trigger()"); - + tb_debug_print("file_trigger()"); return comp_set_state(dev, cmd); } @@ -785,7 +851,7 @@ static int file_get_hw_params(struct comp_dev *dev, struct sof_ipc_stream_params *params, int dir) { struct processing_module *mod = comp_mod(dev); - struct file_comp_data *cd = module_get_private_data(mod); + struct file_comp_data *cd = get_file_comp_data(module_get_private_data(mod)); tb_debug_print("file_hw_params()"); params->direction = dir; diff --git a/tools/testbench/include/testbench/common_test.h b/tools/testbench/include/testbench/common_test.h index 7245eacba367..801d9981a3b9 100644 --- a/tools/testbench/include/testbench/common_test.h +++ b/tools/testbench/include/testbench/common_test.h @@ -32,6 +32,28 @@ struct file_comp_lookup { struct file_state *state; }; +#if CONFIG_IPC_MAJOR_4 + +#define TB_NAME_SIZE 256 +#define TB_MAX_CONFIG_COUNT 2 +#define TB_MAX_CONFIG_NAME_SIZE 64 + +struct tb_mq_desc { + char queue_name[TB_NAME_SIZE]; +}; + +struct tb_config { + char name[TB_MAX_CONFIG_NAME_SIZE]; + unsigned long buffer_frames; + unsigned long buffer_time; + unsigned long period_frames; + unsigned long period_time; + int rate; + int channels; + unsigned long format; +}; +#endif + /* * Global testbench data. * @@ -78,6 +100,21 @@ struct testbench_prm { /* topology */ struct tplg_context tplg; + +#if CONFIG_IPC_MAJOR_4 + struct list_item widget_list; + struct list_item route_list; + struct list_item pcm_list; + struct list_item pipeline_list; + int instance_ids[SND_SOC_TPLG_DAPM_LAST]; + struct tb_mq_desc ipc_tx; + struct tb_mq_desc ipc_rx; + int pcm_id; // TODO: This needs to be cleaned up + struct tplg_pcm_info *pcm_info; + struct tb_config config[TB_MAX_CONFIG_COUNT]; + int num_configs; + size_t period_size; +#endif }; extern int debug; diff --git a/tools/testbench/include/testbench/file.h b/tools/testbench/include/testbench/file.h index 438c7b519d21..869772b5ee62 100644 --- a/tools/testbench/include/testbench/file.h +++ b/tools/testbench/include/testbench/file.h @@ -13,6 +13,8 @@ #include +#define FILE_MAX_COPIES_TIMEOUT 3 + /**< Convert with right shift a bytes count to samples count */ #define FILE_BYTES_TO_S16_SAMPLES(s) ((s) >> 1) #define FILE_BYTES_TO_S32_SAMPLES(s) ((s) >> 2) @@ -40,6 +42,7 @@ struct file_state { enum file_format f_format; bool reached_eof; bool write_failed; + bool copy_timeout; }; struct file_comp_data; @@ -58,8 +61,23 @@ struct file_comp_data { int max_samples; int max_copies; int max_frames; + int copies_timeout_count; }; void sys_comp_module_file_interface_init(void); +/* Get file comp data from copier data */ +static inline struct file_comp_data *get_file_comp_data(struct copier_data *ccd) +{ + struct file_comp_data *cd = (struct file_comp_data *)ccd->ipcgtw_data; + + return cd; +} + +/* Set file comp data to copier data */ +static inline void file_set_comp_data(struct copier_data *ccd, struct file_comp_data *cd) +{ + ccd->ipcgtw_data = (struct ipcgtw_data *)cd; +} + #endif /* _TESTBENCH_FILE */ diff --git a/tools/testbench/include/testbench/file_ipc4.h b/tools/testbench/include/testbench/file_ipc4.h new file mode 100644 index 000000000000..30ebd05f775d --- /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. All rights reserved. + */ + +#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..c4e934f910a7 --- /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. All rights reserved. + */ + +#ifndef _TESTBENCH_TOPOLOGY_IPC4_H +#define _TESTBENCH_TOPOLOGY_IPC4_H + +#include +#include "testbench/common_test.h" + +#define TB_IPC4_MAX_TPLG_OBJECT_SIZE 4096 +#define TB_IPC4_MAX_MSG_SIZE 384 + +int tb_delete_pipeline(struct testbench_prm *tp, struct tplg_pipeline_info *pipe_info); +int tb_free_all_pipelines(struct testbench_prm *tp); +int tb_free_route(struct testbench_prm *tp, struct tplg_route_info *route_info); +int tb_get_instance_id_from_pipeline_id(struct testbench_prm *tp, int id); +int tb_is_single_format(struct sof_ipc4_pin_format *fmts, int num_formats); +int tb_match_audio_format(struct testbench_prm *tp, struct tplg_comp_info *comp_info, + struct tb_config *config); +int tb_new_aif_in_out(struct testbench_prm *tp, int dir); +int tb_new_dai_in_out(struct testbench_prm *tp, int dir); +int tb_new_pga(struct testbench_prm *tp); +int tb_new_process(struct testbench_prm *tp); +int tb_pipelines_set_state(struct testbench_prm *tp, int state, int dir); +int tb_set_reset_state(struct testbench_prm *tp); +int tb_set_running_state(struct testbench_prm *tp); +int tb_set_up_pipeline(struct testbench_prm *tp, struct tplg_pipeline_info *pipe_info); +int tb_set_up_route(struct testbench_prm *tp, struct tplg_route_info *route_info); +int tb_set_up_widget_base_config(struct testbench_prm *tp, + struct tplg_comp_info *comp_info); +int tb_set_up_widget_ipc(struct testbench_prm *tp, struct tplg_comp_info *comp_info); +void tb_free_topology(struct testbench_prm *tp); +void tb_pipeline_update_resource_usage(struct testbench_prm *tp, + struct tplg_comp_info *comp_info); + +#endif /* _TESTBENCH_TOPOLOGY_IPC4_H */ diff --git a/tools/testbench/topology_ipc4.c b/tools/testbench/topology_ipc4.c new file mode 100644 index 000000000000..799e68f5eace --- /dev/null +++ b/tools/testbench/topology_ipc4.c @@ -0,0 +1,1304 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2018-2024 Intel Corporation. All rights reserved. +// +// Author: Ranjani Sridharan +// Liam Girdwood +// Seppo Ingalsuo + +#if CONFIG_IPC_MAJOR_4 + +#include +#include +#include +#include +#include + +#include "testbench/common_test.h" +#include "testbench/topology_ipc4.h" +#include "testbench/file.h" +#include "testbench/file_ipc4.h" +#include "ipc4/pipeline.h" + +#define SOF_IPC4_FW_PAGE(x) ((((x) + BIT(12) - 1) & ~(BIT(12) - 1)) >> 12) +#define SOF_IPC4_FW_ROUNDUP(x) (((x) + BIT(6) - 1) & (~(BIT(6) - 1))) +#define SOF_IPC4_MODULE_INSTANCE_LIST_ITEM_SIZE 12 +#define SOF_IPC4_PIPELINE_OBJECT_SIZE 448 +#define SOF_IPC4_DATA_QUEUE_OBJECT_SIZE 128 +#define SOF_IPC4_LL_TASK_OBJECT_SIZE 72 +#define SOF_IPC4_LL_TASK_LIST_ITEM_SIZE 12 +#define SOF_IPC4_FW_MAX_QUEUE_COUNT 8 + +static const struct sof_topology_token ipc4_comp_tokens[] = { + {SOF_TKN_COMP_IS_PAGES, SND_SOC_TPLG_TUPLE_TYPE_WORD, tplg_token_get_uint32_t, + offsetof(struct ipc4_base_module_cfg, is_pages)}, +}; + +/* + * IPC + */ + +static int tb_ipc_message(void *mailbox, size_t bytes) +{ + struct ipc *ipc = ipc_get(); + + /* reply is copied back to mailbox */ + memcpy(ipc->comp_data, mailbox, bytes); + ipc_cmd(mailbox); + memcpy(mailbox, ipc->comp_data, bytes); + + return 0; +} + +static int tb_mq_cmd_tx_rx(struct tb_mq_desc *ipc_tx, struct tb_mq_desc *ipc_rx, + void *msg, size_t len, void *reply, size_t rlen) +{ + char mailbox[TB_IPC4_MAX_MSG_SIZE]; + struct ipc4_message_reply *reply_msg = reply; + + if (len > TB_IPC4_MAX_MSG_SIZE || rlen > TB_IPC4_MAX_MSG_SIZE) { + fprintf(stderr, "ipc: message too big len=%zu rlen=%zu\n", len, rlen); + return -EINVAL; + } + + memset(mailbox, 0, TB_IPC4_MAX_MSG_SIZE); + memcpy(mailbox, msg, len); + tb_ipc_message(mailbox, len); + memcpy(reply, mailbox, rlen); + if (reply_msg->primary.r.status != IPC4_SUCCESS) + return -EINVAL; + + return 0; +} + +static int tb_parse_ipc4_comp_tokens(struct testbench_prm *tp, + struct ipc4_base_module_cfg *base_cfg) +{ + struct tplg_context *ctx = &tp->tplg; + struct tplg_comp_info *comp_info = ctx->current_comp_info; + struct snd_soc_tplg_vendor_array *array = &ctx->widget->priv.array[0]; + int size = ctx->widget->priv.size; + int ret; + + ret = sof_parse_token_sets(base_cfg, ipc4_comp_tokens, ARRAY_SIZE(ipc4_comp_tokens), + array, size, 1, 0); + if (ret < 0) + return ret; + + return sof_parse_tokens(&comp_info->uuid, comp_ext_tokens, + ARRAY_SIZE(comp_ext_tokens), array, size); +} + +static void tb_setup_widget_ipc_msg(struct tplg_comp_info *comp_info) +{ + struct ipc4_module_init_instance *module_init = &comp_info->module_init; + + module_init->primary.r.type = SOF_IPC4_MOD_INIT_INSTANCE; + module_init->primary.r.module_id = comp_info->module_id; + module_init->primary.r.instance_id = comp_info->instance_id; + module_init->primary.r.msg_tgt = SOF_IPC4_MESSAGE_TARGET_MODULE_MSG; + module_init->primary.r.rsp = SOF_IPC4_MESSAGE_DIR_MSG_REQUEST; +} + +int tb_set_up_widget_ipc(struct testbench_prm *tp, struct tplg_comp_info *comp_info) +{ + struct ipc4_module_init_instance *module_init = &comp_info->module_init; + struct ipc4_message_reply reply; + void *msg; + int size; + int ret = 0; + + module_init->extension.r.param_block_size = comp_info->ipc_size >> 2; + module_init->extension.r.ppl_instance_id = comp_info->pipe_info->instance_id; + + size = sizeof(*module_init) + comp_info->ipc_size; + msg = calloc(size, 1); + if (!msg) + return -ENOMEM; + + memcpy(msg, module_init, sizeof(*module_init)); + memcpy(msg + sizeof(*module_init), comp_info->ipc_payload, comp_info->ipc_size); + + // TODO + 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; + + // TODO + 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"); + goto out; + } + +out: + 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"); + goto out; + } + +out: + 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) { + free(comp_info->ipc_payload); + return -ENOMEM; + } + + 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) { + free(comp_info->ipc_payload); + 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) + return -ENOMEM; + + /* 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 sname_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 sname_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; + +sname_err: + free(comp_info->name); + +err: + 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"); + ret = -EINVAL; + goto exit; + } + 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"); + ret = -EINVAL; + goto exit; + } + 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"); + ret = -EINVAL; + goto exit; + } + 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"); + ret = -EINVAL; + goto exit; + } + 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"); + ret = -EINVAL; + goto exit; + } + break; + case SND_SOC_TPLG_DAPM_BUFFER: + if (tb_new_buffer(tp) < 0) { + fprintf(stderr, "error: load buffer\n"); + ret = -EINVAL; + goto exit; + } + break; + + case SND_SOC_TPLG_DAPM_SCHEDULER: + if (tb_new_pipeline(tp) < 0) { + fprintf(stderr, "error: load pipeline\n"); + ret = -EINVAL; + goto exit; + } + break; + + case SND_SOC_TPLG_DAPM_SRC: + if (tb_new_src(tp) < 0) { + fprintf(stderr, "error: load src\n"); + ret = -EINVAL; + goto exit; + } + break; + case SND_SOC_TPLG_DAPM_ASRC: + if (tb_new_asrc(tp) < 0) { + fprintf(stderr, "error: load src\n"); + ret = -EINVAL; + goto exit; + } + break; + case SND_SOC_TPLG_DAPM_MIXER: + if (tb_new_mixer(tp) < 0) { + fprintf(stderr, "error: load mixer\n"); + ret = -EINVAL; + goto exit; + } + break; + case SND_SOC_TPLG_DAPM_EFFECT: + if (tb_new_process(tp) < 0) { + fprintf(stderr, "error: load effect\n"); + ret = -EINVAL; + goto exit; + } + 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; + } + + ret = 1; + +exit: + return ret; +} + +/* 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"); + return ret; + } + ctx->comp_id++; + } + break; + + /* set up component connections from pipeline graph */ + case SND_SOC_TPLG_TYPE_DAPM_GRAPH: + if (tb_register_graph(tp, hdr->count) < 0) { + fprintf(stderr, "error: pipeline graph\n"); + ret = -EINVAL; + goto out; + } + break; + + case SND_SOC_TPLG_TYPE_PCM: + ret = tb_parse_pcm(tp, hdr->count); + if (ret < 0) + 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); + } + } + +out: + /* free all data */ + + // TODO: Check this + // free(ctx->tplg_base); + return ret; +} + +/* + * To run + */ + +int tb_set_running_state(struct testbench_prm *tp) +{ + int ret; + + ret = tb_pipelines_set_state(tp, SOF_IPC4_PIPELINE_STATE_PAUSED, SOF_IPC_STREAM_PLAYBACK); + if (ret) { + fprintf(stderr, "error: failed to set state to paused\n"); + return ret; + } + + ret = tb_pipelines_set_state(tp, SOF_IPC4_PIPELINE_STATE_PAUSED, SOF_IPC_STREAM_CAPTURE); + if (ret) { + fprintf(stderr, "error: failed to set state to paused\n"); + return ret; + } + + fprintf(stdout, "pipelines are set to paused state\n"); + ret = tb_pipelines_set_state(tp, SOF_IPC4_PIPELINE_STATE_RUNNING, SOF_IPC_STREAM_PLAYBACK); + if (ret) { + fprintf(stderr, "error: failed to set state to running\n"); + return ret; + } + + ret = tb_pipelines_set_state(tp, SOF_IPC4_PIPELINE_STATE_RUNNING, SOF_IPC_STREAM_CAPTURE); + if (ret) + fprintf(stderr, "error: failed to set state to running\n"); + + fprintf(stdout, "pipelines are set to running state\n"); + return ret; +} + +/* + * To stop + */ + +int tb_set_reset_state(struct testbench_prm *tp) +{ + int ret; + + ret = tb_pipelines_set_state(tp, SOF_IPC4_PIPELINE_STATE_PAUSED, SOF_IPC_STREAM_PLAYBACK); + if (ret) { + fprintf(stderr, "error: failed to set state to paused\n"); + return ret; + } + + tb_schedule_pipeline_check_state(tp); + + ret = tb_pipelines_set_state(tp, SOF_IPC4_PIPELINE_STATE_RESET, SOF_IPC_STREAM_PLAYBACK); + if (ret) { + fprintf(stderr, "error: failed to set state to reset\n"); + return ret; + } + + tb_schedule_pipeline_check_state(tp); + + ret = tb_pipelines_set_state(tp, SOF_IPC4_PIPELINE_STATE_PAUSED, SOF_IPC_STREAM_CAPTURE); + if (ret) { + fprintf(stderr, "error: failed to set state to paused\n"); + return ret; + } + + tb_schedule_pipeline_check_state(tp); + + ret = tb_pipelines_set_state(tp, SOF_IPC4_PIPELINE_STATE_RESET, SOF_IPC_STREAM_CAPTURE); + if (ret) + fprintf(stderr, "error: failed to set state to reset\n"); + + tb_schedule_pipeline_check_state(tp); + return ret; +} + +int tb_delete_pipeline(struct testbench_prm *tp, struct tplg_pipeline_info *pipe_info) +{ + struct ipc4_pipeline_delete msg = {{0}}; + struct ipc4_message_reply reply; + int ret; + + msg.primary.r.type = SOF_IPC4_GLB_DELETE_PIPELINE; + msg.primary.r.msg_tgt = SOF_IPC4_MESSAGE_TARGET_FW_GEN_MSG; + msg.primary.r.rsp = SOF_IPC4_MESSAGE_DIR_MSG_REQUEST; + msg.primary.r.instance_id = pipe_info->instance_id; + + ret = tb_mq_cmd_tx_rx(&tp->ipc_tx, &tp->ipc_rx, &msg, sizeof(msg), &reply, sizeof(reply)); + if (ret < 0) { + fprintf(stderr, "error: can't delete pipeline %s\n", pipe_info->name); + return ret; + } + + if (reply.primary.r.status != IPC4_SUCCESS) { + fprintf(stderr, "pipeline %s instance ID %d delete failed with status %d\n", + pipe_info->name, pipe_info->instance_id, reply.primary.r.status); + return -EINVAL; + } + + tplg_debug("pipeline %s instance_id %d freed\n", pipe_info->name, + pipe_info->instance_id); + + return 0; +} + +int tb_free_route(struct testbench_prm *tp, struct tplg_route_info *route_info) +{ + struct tplg_comp_info *src_comp_info = route_info->source; + struct tplg_comp_info *sink_comp_info = route_info->sink; + struct ipc4_module_bind_unbind bu = {{0}}; + struct ipc4_message_reply reply; + int ret; + + /* only unbind when widgets belong to separate pipelines */ + if (src_comp_info->pipeline_id == sink_comp_info->pipeline_id) + return 0; + + bu.primary.r.module_id = src_comp_info->module_id; + bu.primary.r.instance_id = src_comp_info->instance_id; + bu.primary.r.type = SOF_IPC4_MOD_UNBIND; + bu.primary.r.msg_tgt = SOF_IPC4_MESSAGE_TARGET_MODULE_MSG; + bu.primary.r.rsp = SOF_IPC4_MESSAGE_DIR_MSG_REQUEST; + + bu.extension.r.dst_module_id = sink_comp_info->module_id; + bu.extension.r.dst_instance_id = sink_comp_info->instance_id; + + /* FIXME: assign queue ID for components with multiple inputs/outputs */ + bu.extension.r.dst_queue = 0; + bu.extension.r.src_queue = 0; + + ret = tb_mq_cmd_tx_rx(&tp->ipc_tx, &tp->ipc_rx, &bu, sizeof(bu), &reply, sizeof(reply)); + if (ret < 0) { + fprintf(stderr, "error: can't set up route %s -> %s\n", src_comp_info->name, + sink_comp_info->name); + return ret; + } + + if (reply.primary.r.status != IPC4_SUCCESS) { + fprintf(stderr, "route %s -> %s ID set up failed with status %d\n", + src_comp_info->name, sink_comp_info->name, reply.primary.r.status); + return -EINVAL; + } + + tplg_debug("route %s -> %s freed\n", src_comp_info->name, sink_comp_info->name); + + return 0; +} + +#endif /* CONFIG_IPC_MAJOR_4 */ diff --git a/uuid-registry.txt b/uuid-registry.txt index b597e27dcc0e..bbf92ecd2ec2 100644 --- a/uuid-registry.txt +++ b/uuid-registry.txt @@ -145,6 +145,7 @@ c1c5326d-8390-46b4-aa4795c3beca6550 src e61bb28d-149a-4c1f-b70946823ef5f5ae src4 33441051-44cd-466a-83a3178478708aea src_lite eb0bd14b-7d5e-4dfa-bbe27762adb279f0 swaudiodai +37c196ae-3532-4282-8a78dd9d50cc7123 testbench dd511749-d9fa-455c-b3a713585693f1af tdfb 04e3f894-2c5c-4f2e-8dc1694eeaab53fa tone 42f8060c-832f-4dbf-b24751e961997b34 up_down_mixer