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