diff --git a/src/arch/host/CMakeLists.txt b/src/arch/host/CMakeLists.txt index 75314f00f556..cad2e88f7ce1 100644 --- a/src/arch/host/CMakeLists.txt +++ b/src/arch/host/CMakeLists.txt @@ -18,7 +18,7 @@ if (supports_implicit_fallthrough) endif() # C & ASM flags -target_compile_options(sof_options INTERFACE -g -O3 -fPIC -DPIC -std=c99 -std=gnu99 -fgnu89-inline +target_compile_options(sof_options INTERFACE -g -fPIC -DPIC -std=c99 -std=gnu99 -fgnu89-inline -Wall -Werror -Wmissing-prototypes ${implicit_fallthrough} -Wno-pointer-to-int-cast -Wno-int-to-pointer-cast -Wpointer-arith -DCONFIG_LIBRARY "-imacros${CONFIG_H_PATH}") diff --git a/src/arch/host/configs/library_defconfig b/src/arch/host/configs/library_defconfig index 81226d252c82..0ed133a20631 100644 --- a/src/arch/host/configs/library_defconfig +++ b/src/arch/host/configs/library_defconfig @@ -21,7 +21,8 @@ CONFIG_COMP_VOLUME=y CONFIG_COMP_VOLUME_LINEAR_RAMP=y CONFIG_COMP_VOLUME_WINDOWS_FADE=y CONFIG_DEBUG_MEMORY_USAGE_SCAN=n -CONFIG_IPC_MAJOR_3=y +#CONFIG_IPC_MAJOR_3=y +CONFIG_IPC_MAJOR_4=y CONFIG_LIBRARY=y CONFIG_LIBRARY_STATIC=y CONFIG_MATH_IIR_DF2T=y diff --git a/src/audio/crossover/crossover_ipc4.c b/src/audio/crossover/crossover_ipc4.c index d0a8fe4adab2..82586c0499d3 100644 --- a/src/audio/crossover/crossover_ipc4.c +++ b/src/audio/crossover/crossover_ipc4.c @@ -29,7 +29,7 @@ int crossover_get_sink_id(struct comp_data *cd, uint32_t pipeline_id, uint32_t i * kernel know that the base_cfg_ext needs to be appended to the IPC payload. The * Extension is needed to know the output pin indices. */ -int crossover_init_output_pins(struct processing_module *mod) +static int crossover_init_output_pins(struct processing_module *mod) { struct comp_data *cd = module_get_private_data(mod); struct comp_dev *dev = mod->dev; diff --git a/src/audio/pipeline/pipeline-stream.c b/src/audio/pipeline/pipeline-stream.c index add2dbd787b6..f3b506ff31c8 100644 --- a/src/audio/pipeline/pipeline-stream.c +++ b/src/audio/pipeline/pipeline-stream.c @@ -486,6 +486,7 @@ static int pipeline_comp_trigger(struct comp_dev *current, * Initialization delay is only used with SSP, where we * don't use more than one DAI per copier */ +#if !CONFIG_LIBRARY struct dai_data *dd; #if CONFIG_IPC_MAJOR_3 dd = comp_get_drvdata(current); @@ -498,6 +499,7 @@ static int pipeline_comp_trigger(struct comp_dev *current, #error Unknown IPC major version #endif ppl_data->delay_ms = dai_get_init_delay_ms(dd->dai); +#endif } break; default: diff --git a/src/include/ipc/topology.h b/src/include/ipc/topology.h index b2cd833cf1fb..a83b806a3467 100644 --- a/src/include/ipc/topology.h +++ b/src/include/ipc/topology.h @@ -243,8 +243,16 @@ struct sof_ipc_comp_process { /* IPC file component used by testbench only */ struct sof_ipc_comp_file { + /* These need to be the same as in above sof_ipc_comp_process */ struct sof_ipc_comp comp; struct sof_ipc_comp_config config; + uint32_t size; /**< size of bespoke data section in bytes */ + uint32_t type; /**< sof_ipc_process_type */ + + /* reserved for future use */ + uint32_t reserved[7]; + + /* These are additional parameters for file */ uint32_t rate; uint32_t channels; char *fn; diff --git a/src/include/ipc4/alh.h b/src/include/ipc4/alh.h index c9658c225d76..37606c5c8691 100644 --- a/src/include/ipc4/alh.h +++ b/src/include/ipc4/alh.h @@ -24,6 +24,7 @@ #ifndef __SOF_IPC4_ALH_H__ #define __SOF_IPC4_ALH_H__ +#include <stddef.h> #include <stdint.h> #include <stdbool.h> #include <ipc4/gateway.h> diff --git a/src/include/sof/audio/component.h b/src/include/sof/audio/component.h index c10b3bf67858..788d85ae19e4 100644 --- a/src/include/sof/audio/component.h +++ b/src/include/sof/audio/component.h @@ -744,6 +744,7 @@ void sys_comp_host_init(void); void sys_comp_kpb_init(void); void sys_comp_selector_init(void); +void sys_comp_module_copier_interface_init(void); void sys_comp_module_crossover_interface_init(void); void sys_comp_module_dcblock_interface_init(void); void sys_comp_module_demux_interface_init(void); @@ -751,15 +752,19 @@ void sys_comp_module_drc_interface_init(void); void sys_comp_module_dts_interface_init(void); void sys_comp_module_eq_fir_interface_init(void); void sys_comp_module_eq_iir_interface_init(void); +void sys_comp_module_gain_interface_init(void); void sys_comp_module_google_rtc_audio_processing_interface_init(void); void sys_comp_module_google_ctc_audio_processing_interface_init(void); void sys_comp_module_igo_nr_interface_init(void); void sys_comp_module_mfcc_interface_init(void); void sys_comp_module_mixer_interface_init(void); +void sys_comp_module_mixin_interface_init(void); +void sys_comp_module_mixout_interface_init(void); void sys_comp_module_multiband_drc_interface_init(void); void sys_comp_module_mux_interface_init(void); void sys_comp_module_asrc_interface_init(void); void sys_comp_module_rtnr_interface_init(void); +void sys_comp_module_selector_interface_init(void); void sys_comp_module_src_interface_init(void); void sys_comp_module_tdfb_interface_init(void); void sys_comp_module_volume_interface_init(void); diff --git a/src/include/sof/audio/ipc-config.h b/src/include/sof/audio/ipc-config.h index ccabb8628e35..f0c74f2da2a2 100644 --- a/src/include/sof/audio/ipc-config.h +++ b/src/include/sof/audio/ipc-config.h @@ -143,6 +143,7 @@ struct ipc_config_process { /* file IO ipc comp */ struct ipc_comp_file { + struct ipc_config_process module_header; /* Needed for module_adapter_init_data() */ uint32_t rate; uint32_t channels; char *fn; diff --git a/src/include/sof/lib/dai-legacy.h b/src/include/sof/lib/dai-legacy.h index 328ac156dc05..e20a6d2e8e53 100644 --- a/src/include/sof/lib/dai-legacy.h +++ b/src/include/sof/lib/dai-legacy.h @@ -163,6 +163,10 @@ struct llp_slot_info { uint32_t reg_offset; }; +typedef int (*channel_copy_func)(const struct audio_stream *src, unsigned int src_channel, + struct audio_stream *dst, unsigned int dst_channel, + unsigned int frames); + /** * \brief DAI runtime data */ @@ -181,6 +185,12 @@ struct dai_data { int xrun; /* true if we are doing xrun recovery */ pcm_converter_func process; /* processing function */ + uint32_t chmap; + channel_copy_func channel_copy; /* channel copy func used by multi-endpoint + * gateway to mux/demux stream from/to multiple + * DMA buffers + */ + uint32_t period_bytes; /* number of bytes per one period */ uint64_t total_data_processed; diff --git a/src/ipc/ipc3/helper.c b/src/ipc/ipc3/helper.c index 13c1824e627d..602230cfaec1 100644 --- a/src/ipc/ipc3/helper.c +++ b/src/ipc/ipc3/helper.c @@ -196,10 +196,9 @@ static int comp_specific_builder(struct sof_ipc_comp *comp, { #if CONFIG_LIBRARY struct sof_ipc_comp_file *file = (struct sof_ipc_comp_file *)comp; -#else +#endif struct sof_ipc_comp_host *host = (struct sof_ipc_comp_host *)comp; struct sof_ipc_comp_dai *dai = (struct sof_ipc_comp_dai *)comp; -#endif struct sof_ipc_comp_volume *vol = (struct sof_ipc_comp_volume *)comp; struct sof_ipc_comp_process *proc = (struct sof_ipc_comp_process *)comp; struct sof_ipc_comp_src *src = (struct sof_ipc_comp_src *)comp; @@ -211,20 +210,22 @@ static int comp_specific_builder(struct sof_ipc_comp *comp, switch (comp->type) { #if CONFIG_LIBRARY /* test bench maps host and DAIs to a file */ - case SOF_COMP_HOST: - case SOF_COMP_SG_HOST: - case SOF_COMP_DAI: - case SOF_COMP_SG_DAI: - if (IPC_TAIL_IS_SIZE_INVALID(*file)) - return -EBADMSG; + case SOF_COMP_FILEREAD: + case SOF_COMP_FILEWRITE: config->file.channels = file->channels; config->file.fn = file->fn; config->file.frame_fmt = file->frame_fmt; config->file.mode = file->mode; config->file.rate = file->rate; config->file.direction = file->direction; + + /* For module_adapter_init_data() ipc_module_adapter compatibility */ + config->file.module_header.type = proc->type; + config->file.module_header.size = proc->size; + config->file.module_header.data = (uint8_t *)proc->data - + sizeof(struct ipc_config_process); break; -#else +#endif case SOF_COMP_HOST: case SOF_COMP_SG_HOST: if (IPC_TAIL_IS_SIZE_INVALID(*host)) @@ -241,7 +242,6 @@ static int comp_specific_builder(struct sof_ipc_comp *comp, config->dai.direction = dai->direction; config->dai.type = dai->type; break; -#endif case SOF_COMP_VOLUME: if (IPC_TAIL_IS_SIZE_INVALID(*vol)) return -EBADMSG; diff --git a/src/ipc/ipc4/handler.c b/src/ipc/ipc4/handler.c index 54ef9d396b52..75693f87618c 100644 --- a/src/ipc/ipc4/handler.c +++ b/src/ipc/ipc4/handler.c @@ -534,6 +534,9 @@ static void ipc_compound_msg_done(uint32_t msg_id, int error) static int ipc_wait_for_compound_msg(void) { +#if CONFIG_LIBRARY + atomic_set(&msg_data.delayed_reply, 0); +#else int try_count = 30; while (atomic_read(&msg_data.delayed_reply)) { @@ -545,6 +548,7 @@ static int ipc_wait_for_compound_msg(void) return IPC4_FAILURE; } } +#endif return IPC4_SUCCESS; } diff --git a/src/ipc/ipc4/helper.c b/src/ipc/ipc4/helper.c index d427643ef3ea..898c6a763564 100644 --- a/src/ipc/ipc4/helper.c +++ b/src/ipc/ipc4/helper.c @@ -981,6 +981,16 @@ static const struct ipc4_module_uuid uuid_map[] = { .d = { 0xa0, 0x8f, 0x97, 0xfc, 0xc4, 0x2e, 0xaa, 0xeb }}}, /* ALSA aplay */ {0x99, {.a = 0x66def9f0, .b = 0x39f2, .c = 0x11ed, .d = { 0xf7, 0x89, 0xaf, 0x98, 0xa6, 0x44, 0x0c, 0xc4 }}}, /* ALSA arecord */ + {0x9a, {.a = 0xbfc7488c, .b = 0x75aa, .c = 0x4ce8, + .d = { 0x9d, 0xbe, 0xd8, 0xda, 0x08, 0xa6, 0x98, 0xc2 }}}, /* FILE component */ + {0x9b, {.a = 0xbfc7488c, .b = 0x75aa, .c = 0x4ce8, + .d = { 0x9d, 0xbe, 0xd8, 0xda, 0x08, 0xa6, 0x98, 0xc2 }}}, /* FILE component */ + {0x9c, {.a = 0xbfc7488c, .b = 0x75aa, .c = 0x4ce8, + .d = { 0x9d, 0xbe, 0xd8, 0xda, 0x08, 0xa6, 0x98, 0xc2 }}}, /* FILE component */ + {0x9d, {.a = 0xbfc7488c, .b = 0x75aa, .c = 0x4ce8, + .d = { 0x9d, 0xbe, 0xd8, 0xda, 0x08, 0xa6, 0x98, 0xc2 }}}, /* FILE component */ + {0x9e, {.a = 0xb809efaf, .b = 0x5681, .c = 0x42b1, + .d = { 0x9e, 0xd6, 0x04, 0xbb, 0x01, 0x2d, 0xd3, 0x84 }}}, /* dcblock */ }; static const struct comp_driver *ipc4_library_get_drv(int module_id) diff --git a/src/platform/library/include/platform/lib/ll_schedule.h b/src/platform/library/include/platform/lib/ll_schedule.h index be79ccf15d22..06c918b84fdc 100644 --- a/src/platform/library/include/platform/lib/ll_schedule.h +++ b/src/platform/library/include/platform/lib/ll_schedule.h @@ -6,6 +6,7 @@ #ifndef __LIBRARY_INCLUDE_LIB_SCHEDULE_H__ #define __LIBRARY_INCLUDE_LIB_SCHEDULE_H__ +#include <rtos/task.h> #include <stdint.h> struct task; diff --git a/tools/test/audio/comp_run.sh b/tools/test/audio/comp_run.sh index 7e7dd8d5f846..0ade0a4a0176 100755 --- a/tools/test/audio/comp_run.sh +++ b/tools/test/audio/comp_run.sh @@ -114,7 +114,8 @@ run_testbench () delete_file_check "$FN_TRACE" if [ -z "$FN_TRACE" ]; then # shellcheck disable=SC2086 - $VALGRIND_CMD $CMD + #$VALGRIND_CMD $CMD + $CMD 2> /dev/null else $VALGRIND_CMD $CMD 2> "$FN_TRACE" || { local ret=$? diff --git a/tools/testbench/CMakeLists.txt b/tools/testbench/CMakeLists.txt index 8e95a60d6a14..57c48cc99e93 100644 --- a/tools/testbench/CMakeLists.txt +++ b/tools/testbench/CMakeLists.txt @@ -14,8 +14,17 @@ add_executable(testbench common_test.c file.c topology.c + topology_ipc4.c ) +# TODO +#if(CONFIG_IPC_MAJOR_3) +# add_local_sources(testbench topology_ipc3.c) +#elseif(CONFIG_IPC_MAJOR_4) +# add_local_sources(testbench topology_ipc4.c) +#endif() + + sof_append_relative_path_definitions(testbench) target_include_directories(testbench PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include) @@ -41,7 +50,7 @@ if (supports_implicit_fallthrough) set(implicit_fallthrough -Wimplicit-fallthrough) endif() -target_compile_options(testbench PRIVATE -g -O3 -Wall -Werror -Wmissing-prototypes +target_compile_options(testbench PRIVATE -g -Wall -Werror -Wmissing-prototypes ${implicit_fallthrough} -DCONFIG_LIBRARY -DCONFIG_LIBRARY_STATIC -imacros${config_h}) target_link_libraries(testbench PRIVATE -lm) diff --git a/tools/testbench/common_test.c b/tools/testbench/common_test.c index c3497ce582c4..859543d4aeaa 100644 --- a/tools/testbench/common_test.c +++ b/tools/testbench/common_test.c @@ -1,38 +1,48 @@ // SPDX-License-Identifier: BSD-3-Clause // -// Copyright(c) 2018 Intel Corporation. All rights reserved. +// Copyright(c) 2018-2024 Intel Corporation. All rights reserved. -#include <stdint.h> -#include <stddef.h> -#include <time.h> -#include <stdio.h> -#include <stdlib.h> -#include <rtos/string.h> -#include <math.h> -#include <rtos/sof.h> -#include <rtos/task.h> -#include <rtos/alloc.h> -#include <sof/lib/notifier.h> +#include <platform/lib/ll_schedule.h> +#include <module/module/base.h> +#include <sof/audio/component_ext.h> +#include <sof/audio/pipeline.h> #include <sof/ipc/driver.h> #include <sof/ipc/topology.h> #include <sof/lib/agent.h> #include <sof/lib/dai.h> #include <sof/lib/dma.h> +#include <sof/lib/notifier.h> #include <sof/schedule/edf_schedule.h> #include <sof/schedule/ll_schedule.h> #include <sof/schedule/ll_schedule_domain.h> #include <sof/schedule/schedule.h> +#include <rtos/alloc.h> +#include <rtos/sof.h> +#include <rtos/string.h> +#include <rtos/task.h> #include <rtos/wait.h> -#include <sof/audio/pipeline.h> -#include <sof/audio/component_ext.h> +#include <tplg_parser/topology.h> +#include <math.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <time.h> + #include "testbench/common_test.h" +#include "testbench/file.h" #include "testbench/trace.h" -#include <tplg_parser/topology.h> +#include "testbench/topology.h" #if defined __XCC__ #include <xtensa/tie/xt_timer.h> #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) @@ -43,8 +53,6 @@ int tb_setup(struct sof *sof, struct testbench_prm *tp) /* init components */ sys_comp_init(sof); - sys_comp_file_init(); - sys_comp_selector_init(); /* Module adapter components */ sys_comp_module_crossover_interface_init(); @@ -53,12 +61,15 @@ int tb_setup(struct sof *sof, struct testbench_prm *tp) 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(); @@ -74,6 +85,10 @@ int tb_setup(struct sof *sof, struct testbench_prm *tp) 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"); @@ -122,181 +137,261 @@ void tb_free(struct sof *sof) free(sof->ipc); } -/* Get pipeline host component */ -static struct comp_dev *tb_get_pipeline_host(struct pipeline *p) +/* print debug messages */ +void debug_print(char *message) { - struct comp_dev *cd; + if (host_trace_level >= LOG_LEVEL_DEBUG) + printf("debug: %s", message); +} - cd = p->source_comp; - if (cd->direction == SOF_IPC_STREAM_CAPTURE) - cd = p->sink_comp; +/* enable trace in testbench */ +void tb_enable_trace(unsigned int log_level) +{ + host_trace_level = log_level; + if (host_trace_level) + debug_print("trace print enabled\n"); + else + debug_print("trace print disabled\n"); +} - return cd; +void tb_gettime(struct timespec *td) +{ +#if !defined __XCC__ + clock_gettime(CLOCK_MONOTONIC, td); +#else + td->tv_nsec = 0; + td->tv_sec = 0; +#endif } -/* set up pcm params, prepare and trigger pipeline */ -int tb_pipeline_start(struct ipc *ipc, struct pipeline *p) +void tb_getcycles(uint64_t *cycles) { - struct comp_dev *cd; - int ret; +#if defined __XCC__ + *cycles = XT_RSR_CCOUNT(); +#else + *cycles = 0; +#endif +} - /* Get pipeline host component */ - cd = tb_get_pipeline_host(p); +#if DISABLED_CODE +static int tb_get_pipeline_instance_id(struct testbench_prm *tp, int id) +{ + struct tplg_pipeline_info *pipe_info; + struct tplg_pipeline_list *pipeline_list; + int i; + + pipeline_list = &tp->pcm_info->playback_pipeline_list; + for (i = 0; i < pipeline_list->count; i++) { + pipe_info = pipeline_list->pipelines[i]; + if (pipe_info->id == id) + return pipe_info->instance_id; + } - /* Component prepare */ - ret = pipeline_prepare(p, cd); - if (ret < 0) { - fprintf(stderr, "error: Failed prepare pipeline command: %s\n", - strerror(ret)); - return ret; + pipeline_list = &tp->pcm_info->capture_pipeline_list; + for (i = 0; i < pipeline_list->count; i++) { + pipe_info = pipeline_list->pipelines[i]; + if (pipe_info->id == id) + return pipe_info->instance_id; } - /* Start the pipeline */ - ret = pipeline_trigger(cd->pipeline, cd, COMP_TRIGGER_PRE_START); - if (ret < 0) { - fprintf(stderr, "error: Failed to start pipeline command: %s\n", - strerror(ret)); - return ret; + return -EINVAL; +} + +static struct pipeline *tb_get_pipeline_by_id(struct testbench_prm *tb, int pipeline_id) +{ + struct ipc_comp_dev *pipe_dev; + struct ipc *ipc = sof_get()->ipc; + int id = tb_get_pipeline_instance_id(tb, pipeline_id); + + pipe_dev = ipc_get_comp_by_ppl_id(ipc, COMP_TYPE_PIPELINE, id, IPC_COMP_IGNORE_REMOTE); + return pipe_dev->pipeline; +} +#endif + +void tb_show_file_stats(struct testbench_prm *tb, int pipeline_id) +{ + struct ipc_comp_dev *icd; + struct comp_dev *dev; + struct processing_module *mod; + struct file_comp_data *fcd; + int i; + + for (i = 0; i < tb->input_file_num; i++) { + if (tb->fr[i].id < 0 || tb->fr[i].pipeline_id != pipeline_id) + continue; + + icd = ipc_get_comp_by_id(sof_get()->ipc, tb->fr[i].id); + if (!icd) + continue; + + dev = icd->cd; + mod = comp_mod(dev); + fcd = module_get_private_data(mod); + printf("file %s: id %d: type %d: samples %d copies %d\n", + fcd->fs.fn, dev->ipc_config.id, dev->drv->type, fcd->fs.n, + fcd->fs.copy_count); + } + + for (i = 0; i < tb->output_file_num; i++) { + if (tb->fw[i].id < 0 || tb->fw[i].pipeline_id != pipeline_id) + continue; + + icd = ipc_get_comp_by_id(sof_get()->ipc, tb->fw[i].id); + if (!icd) + continue; + + dev = icd->cd; + mod = comp_mod(dev); + fcd = module_get_private_data(mod); + printf("file %s: id %d: type %d: samples %d copies %d\n", + fcd->fs.fn, dev->ipc_config.id, dev->drv->type, fcd->fs.n, + fcd->fs.copy_count); } - return ret; } -/* set up pcm params, prepare and trigger pipeline */ -int tb_pipeline_stop(struct ipc *ipc, struct pipeline *p) +int tb_set_up_all_pipelines(struct testbench_prm *tb) { - struct comp_dev *cd; int ret; - /* Get pipeline host component */ - cd = tb_get_pipeline_host(p); + ret = tb_set_up_pipelines(tb, SOF_IPC_STREAM_PLAYBACK); + if (ret) { + fprintf(stderr, "error: Failed tb_set_up_pipelines for playback\n"); + return ret; + } - ret = pipeline_trigger(cd->pipeline, cd, COMP_TRIGGER_STOP); - if (ret < 0) { - fprintf(stderr, "error: Failed to stop pipeline command: %s\n", - strerror(ret)); + ret = tb_set_up_pipelines(tb, SOF_IPC_STREAM_CAPTURE); + if (ret) { + fprintf(stderr, "error: Failed tb_set_up_pipelines for capture\n"); + return ret; } - return ret; + fprintf(stdout, "pipelines set up complete\n"); + return 0; } -/* set up pcm params, prepare and trigger pipeline */ -int tb_pipeline_reset(struct ipc *ipc, struct pipeline *p) +int tb_load_topology(struct testbench_prm *tb) { - struct comp_dev *cd; + struct tplg_context *ctx = &tb->tplg; int ret; - /* Get pipeline host component */ - cd = tb_get_pipeline_host(p); + /* setup the thread virtual core config */ + memset(ctx, 0, sizeof(*ctx)); + ctx->comp_id = 1; + ctx->core_id = 0; + ctx->sof = sof_get(); + ctx->tplg_file = tb->tplg_file; + if (tb->ipc_version < 3 || tb->ipc_version > 4) { + fprintf(stderr, "error: illegal ipc version\n"); + return -EINVAL; + } + + ctx->ipc_major = tb->ipc_version; - ret = pipeline_reset(p, cd); + /* parse topology file and create pipeline */ + ret = tb_parse_topology(tb); if (ret < 0) - fprintf(stderr, "error: pipeline reset\n"); + fprintf(stderr, "error: parsing topology\n"); - return ret; + debug_print("topology parsing complete\n"); + return 0; } -/* pipeline pcm params */ -int tb_pipeline_params(struct testbench_prm *tp, struct ipc *ipc, struct pipeline *p, - struct tplg_context *ctx) +static bool tb_is_file_component_at_eof(struct testbench_prm *tp) { - struct comp_dev *cd; - struct sof_ipc_pcm_params params = {{0}}; - char message[DEBUG_MSG_LEN]; - int fs_period; - int period; - int ret = 0; - - if (!p) { - fprintf(stderr, "error: pipeline is NULL\n"); - return -EINVAL; + int i; + + for (i = 0; i < tp->input_file_num; i++) { + if (!tp->fr[i].state) + continue; + + if (tp->fr[i].state->reached_eof || tp->fr[i].state->copy_timeout) + return true; } - period = p->period; - - /* Compute period from sample rates */ - fs_period = (int)(0.9999 + tp->fs_in * period / 1e6); - sprintf(message, "period sample count %d\n", fs_period); - debug_print(message); - - /* set pcm params */ - params.comp_id = p->comp_id; - params.params.buffer_fmt = SOF_IPC_BUFFER_INTERLEAVED; - params.params.frame_fmt = tp->frame_fmt; - params.params.rate = tp->fs_in; - params.params.channels = tp->channels_in; - - switch (params.params.frame_fmt) { - case SOF_IPC_FRAME_S16_LE: - params.params.sample_container_bytes = 2; - params.params.sample_valid_bytes = 2; - break; - case SOF_IPC_FRAME_S24_4LE: - params.params.sample_container_bytes = 4; - params.params.sample_valid_bytes = 3; - break; - case SOF_IPC_FRAME_S32_LE: - params.params.sample_container_bytes = 4; - params.params.sample_valid_bytes = 4; - break; - default: - fprintf(stderr, "error: invalid frame format\n"); - return -EINVAL; + for (i = 0; i < tp->output_file_num; i++) { + if (!tp->fw[i].state) + continue; + + if (tp->fw[i].state->reached_eof || tp->fw[i].state->copy_timeout || + tp->fw[i].state->write_failed) + return true; } - params.params.host_period_bytes = fs_period * params.params.channels * - params.params.sample_container_bytes; + return false; +} - /* Get pipeline host component */ - cd = tb_get_pipeline_host(p); +bool tb_schedule_pipeline_check_state(struct testbench_prm *tp) +{ + uint64_t cycles0, cycles1; - /* Set pipeline params direction from scheduling component */ - params.params.direction = cd->direction; + tb_getcycles(&cycles0); - printf("test params: rate %d channels %d format %d\n", - params.params.rate, params.params.channels, - params.params.frame_fmt); + schedule_ll_run_tasks(); - /* pipeline params */ - ret = pipeline_params(p, cd, ¶ms); - if (ret < 0) - fprintf(stderr, "error: pipeline_params\n"); + tb_getcycles(&cycles1); + tp->total_cycles += cycles1 - cycles0; - return ret; + /* Check if all file components are running */ + return tb_is_file_component_at_eof(tp); } -/* print debug messages */ -void debug_print(char *message) +bool tb_is_pipeline_enabled(struct testbench_prm *tb, int pipeline_id) { - if (host_trace_level >= LOG_LEVEL_DEBUG) - printf("debug: %s", message); -} + int i; -/* enable trace in testbench */ -void tb_enable_trace(unsigned int log_level) -{ - host_trace_level = log_level; - if (host_trace_level) - debug_print("trace print enabled\n"); - else - debug_print("trace print disabled\n"); -} + for (i = 0; i < tb->pipeline_num; i++) { + if (tb->pipelines[i] == pipeline_id) + return true; + } -void tb_gettime(struct timespec *td) -{ -#if !defined __XCC__ - clock_gettime(CLOCK_MONOTONIC, td); -#else - td->tv_nsec = 0; - td->tv_sec = 0; -#endif + return false; } -void tb_getcycles(uint64_t *cycles) +int tb_find_file_components(struct testbench_prm *tb) { -#if defined __XCC__ - *cycles = XT_RSR_CCOUNT(); -#else - *cycles = 0; -#endif + struct ipc_comp_dev *icd; + struct processing_module *mod; + struct file_comp_data *fcd; + int i; + + for (i = 0; i < tb->input_file_num; i++) { + if (!tb_is_pipeline_enabled(tb, tb->fr[i].pipeline_id)) { + tb->fr[i].id = -1; + continue; + } + + icd = ipc_get_comp_by_id(sof_get()->ipc, tb->fr[i].id); + if (!icd) { + tb->fr[i].state = NULL; + continue; + } + + mod = comp_mod(icd->cd); + if (!mod) { + fprintf(stderr, "error: null module.\n"); + return -EINVAL; + } + fcd = module_get_private_data(mod); + tb->fr[i].state = &fcd->fs; + } + + for (i = 0; i < tb->output_file_num; i++) { + if (!tb_is_pipeline_enabled(tb, tb->fw[i].pipeline_id)) { + tb->fw[i].id = -1; + continue; + } + + icd = ipc_get_comp_by_id(sof_get()->ipc, tb->fw[i].id); + if (!icd) { + tb->fr[i].state = NULL; + continue; + } + + mod = comp_mod(icd->cd); + fcd = module_get_private_data(mod); + tb->fw[i].state = &fcd->fs; + } + + return 0; } diff --git a/tools/testbench/file.c b/tools/testbench/file.c index 1ce8b578d6c7..d2c54cced41f 100644 --- a/tools/testbench/file.c +++ b/tools/testbench/file.c @@ -1,33 +1,34 @@ // SPDX-License-Identifier: BSD-3-Clause // -// Copyright(c) 2018 Intel Corporation. All rights reserved. +// Copyright(c) 2018-2024 Intel Corporation. All rights reserved. /* file component for reading/writing pcm samples to/from a file */ -#include <stdio.h> -#include <stdint.h> -#include <stddef.h> -#include <stdlib.h> -#include <errno.h> -#include <inttypes.h> -#include <rtos/sof.h> -#include <sof/list.h> -#include <sof/audio/stream.h> -#include <sof/audio/ipc-config.h> -#include <rtos/clk.h> -#include <sof/ipc/driver.h> +#include <sof/audio/module_adapter/module/generic.h> #include <sof/audio/component.h> #include <sof/audio/format.h> +#include <sof/audio/ipc-config.h> #include <sof/audio/pipeline.h> +#include <sof/audio/stream.h> +#include <sof/ipc/driver.h> #include <ipc/stream.h> +#include <rtos/init.h> +#include <rtos/clk.h> +#include <rtos/sof.h> +#include <sof/list.h> +#include <errno.h> +#include <inttypes.h> +#include <stddef.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> #include "testbench/common_test.h" #include "testbench/file.h" +#include "testbench/file_ipc4.h" SOF_DEFINE_REG_UUID(file); DECLARE_TR_CTX(file_tr, SOF_UUID(file_uuid), LOG_LEVEL_INFO); - -static const struct comp_driver comp_file_dai; -static const struct comp_driver comp_file_host; +LOG_MODULE_REGISTER(file, CONFIG_SOF_LOG_LEVEL); /* * Helpers for s24_4le data. To avoid an overflown 24 bit to be taken as valid 32 bit @@ -409,18 +410,16 @@ static int write_samples_s16(struct file_comp_data *cd, struct audio_stream *sou } /* Default file copy function, just return error if called */ -static int file_default(struct comp_dev *dev, struct audio_stream *sink, +static int file_default(struct file_comp_data *cd, struct audio_stream *sink, struct audio_stream *source, uint32_t frames) { return -EINVAL; } /* function for processing 32-bit samples */ -static int file_s32(struct comp_dev *dev, struct audio_stream *sink, +static int file_s32(struct file_comp_data *cd, struct audio_stream *sink, struct audio_stream *source, uint32_t frames) { - struct dai_data *dd = comp_get_drvdata(dev); - struct file_comp_data *cd = comp_get_drvdata(dd->dai); int nch; int n_samples = 0; @@ -450,11 +449,9 @@ static int file_s32(struct comp_dev *dev, struct audio_stream *sink, } /* function for processing 16-bit samples */ -static int file_s16(struct comp_dev *dev, struct audio_stream *sink, +static int file_s16(struct file_comp_data *cd, struct audio_stream *sink, struct audio_stream *source, uint32_t frames) { - struct dai_data *dd = comp_get_drvdata(dev); - struct file_comp_data *cd = comp_get_drvdata(dd->dai); int nch; int n_samples; @@ -484,11 +481,9 @@ static int file_s16(struct comp_dev *dev, struct audio_stream *sink, } /* function for processing 24-bit samples */ -static int file_s24(struct comp_dev *dev, struct audio_stream *sink, +static int file_s24(struct file_comp_data *cd, struct audio_stream *sink, struct audio_stream *source, uint32_t frames) { - struct dai_data *dd = comp_get_drvdata(dev); - struct file_comp_data *cd = comp_get_drvdata(dd->dai); int nch; int n_samples = 0; @@ -530,45 +525,28 @@ static enum file_format get_file_format(char *filename) return FILE_RAW; } -static struct comp_dev *file_new(const struct comp_driver *drv, - const struct comp_ipc_config *config, - const void *spec) +static int file_init(struct processing_module *mod) { - const struct dai_driver *fdrv; - struct comp_dev *dev; - const struct ipc_comp_file *ipc_file = spec; - struct dai_data *dd; - struct dai *fdai; + struct comp_dev *dev = mod->dev; + struct module_data *mod_data = &mod->priv; struct file_comp_data *cd; - debug_print("file_new()\n"); - - dev = comp_alloc(drv, sizeof(*dev)); - if (!dev) - return NULL; - dev->ipc_config = *config; - - /* allocate memory for file comp data */ - dd = rzalloc(SOF_MEM_ZONE_RUNTIME_SHARED, 0, SOF_MEM_CAPS_RAM, sizeof(*dd)); - if (!dd) - goto error_skip_dd; +#if CONFIG_IPC_MAJOR_4 + const struct ipc4_file_module_cfg *module_cfg = + (const struct ipc4_file_module_cfg *)mod_data->cfg.init_data; - fdai = rzalloc(SOF_MEM_ZONE_RUNTIME_SHARED, 0, SOF_MEM_CAPS_RAM, sizeof(*fdai)); - if (!fdai) - goto error_skip_dai; - - fdrv = rzalloc(SOF_MEM_ZONE_RUNTIME_SHARED, 0, SOF_MEM_CAPS_RAM, sizeof(*fdrv)); - if (!fdrv) - goto error_skip_drv; + 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 + debug_print("file_init()\n"); cd = rzalloc(SOF_MEM_ZONE_RUNTIME_SHARED, 0, SOF_MEM_CAPS_RAM, sizeof(*cd)); if (!cd) - goto error_skip_cd; + return -ENOMEM; - fdai->drv = fdrv; - dd->dai = fdai; - comp_set_drvdata(dev, dd); - comp_set_drvdata(dd->dai, cd); + mod_data->private = cd; /* default function for processing samples */ cd->file_func = file_default; @@ -586,10 +564,17 @@ static struct comp_dev *file_new(const struct comp_driver *drv, 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: + /* 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; + cd->fs.rfh = fopen(cd->fs.fn, "r"); if (!cd->fs.rfh) { fprintf(stderr, "error: opening file %s for reading - %s\n", @@ -598,6 +583,12 @@ static struct comp_dev *file_new(const struct comp_driver *drv, } 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; + cd->fs.wfh = fopen(cd->fs.fn, "w+"); if (!cd->fs.wfh) { fprintf(stderr, "error: opening file %s for writing - %s\n", @@ -613,35 +604,23 @@ static struct comp_dev *file_new(const struct comp_driver *drv, 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; - dev->state = COMP_STATE_READY; - return dev; + + return 0; error: free(cd); - -error_skip_cd: - free((void *)fdrv); - -error_skip_drv: - free(fdai); - -error_skip_dai: - free(dd); - -error_skip_dd: - free(dev); - return NULL; + return -EINVAL; } -static void file_free(struct comp_dev *dev) +static int file_free(struct processing_module *mod) { - struct dai_data *dd = comp_get_drvdata(dev); - struct file_comp_data *cd = comp_get_drvdata(dd->dai); + struct file_comp_data *cd = module_get_private_data(mod); - comp_dbg(dev, "file_free()"); + debug_print("file_free()\n"); if (cd->fs.mode == FILE_READ) fclose(cd->fs.rfh); @@ -650,64 +629,98 @@ static void file_free(struct comp_dev *dev) free(cd->fs.fn); free(cd); - free((void *)dd->dai->drv); - free(dd->dai); - free(dd); - free(dev); + return 0; } -static int file_verify_params(struct comp_dev *dev, - struct sof_ipc_stream_params *params) +/* + * copy and process stream samples + * returns the number of bytes copied + */ +static int file_process(struct processing_module *mod, + struct input_stream_buffer *input_buffers, int num_input_buffers, + struct output_stream_buffer *output_buffers, int num_output_buffers) { - int ret; + struct comp_dev *dev = mod->dev; + struct file_comp_data *cd = 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 ret = 0; + + if (cd->fs.reached_eof) + return -ENODATA; + + /* Note: a SOF_COMP_DAI does not have input_buffers and output buffers set */ + tb_getcycles(&cycles0); + switch (cd->fs.mode) { + case FILE_READ: + /* read PCM samples from file */ + buffer = list_first_item(&dev->bsink_list, struct comp_buffer, source_list); + sink = &buffer->stream; + frames = audio_stream_get_free_frames(sink); + frames = MIN(frames, cd->max_frames); + samples = cd->file_func(cd, sink, NULL, frames); + audio_stream_produce(sink, audio_stream_sample_bytes(sink) * samples); + break; + case FILE_WRITE: + /* write PCM samples into file */ + buffer = list_first_item(&dev->bsource_list, struct comp_buffer, sink_list); + source = &buffer->stream; + frames = audio_stream_get_avail_frames(source); + frames = MIN(frames, cd->max_frames); + samples = cd->file_func(cd, NULL, source, frames); + audio_stream_consume(source, audio_stream_sample_bytes(source) * samples); + break; + default: + /* TODO: duplex mode */ + ret = -EINVAL; + break; + } - comp_dbg(dev, "file_verify_params()"); + cd->fs.copy_count++; - ret = comp_verify_params(dev, 0, params); - if (ret < 0) { - comp_err(dev, "file_verify_params() error: comp_verify_params() failed."); - return ret; + if (cd->fs.reached_eof || (cd->max_copies && cd->fs.copy_count >= cd->max_copies)) { + cd->fs.reached_eof = true; + debug_print("file_process(): reached EOF or max_copies\n"); } - return 0; + if (samples) { + cd->copies_timeout = 0; + } else { + cd->copies_timeout++; + if (cd->copies_timeout == FILE_MAX_COPIES_TIMEOUT) { + debug_print("file_process(): copies_timeout reached\n"); + cd->fs.copy_timeout = true; + } + } + + tb_getcycles(&cycles1); + cd->fs.cycles_count += cycles1 - cycles0; + return ret; } -/** - * \brief Sets file component audio stream parameters. - * \param[in,out] dev Volume base component device. - * \param[in] params Audio (PCM) stream parameters (ignored for this component) - * \return Error code. - * - * All done in prepare() since we need to know source and sink component params. - */ -static int file_params(struct comp_dev *dev, - struct sof_ipc_stream_params *params) +static int file_prepare(struct processing_module *mod, + struct sof_source **sources, int num_of_sources, + struct sof_sink **sinks, int num_of_sinks) { - struct comp_buffer *buffer; - struct dai_data *dd = comp_get_drvdata(dev); - struct file_comp_data *cd = comp_get_drvdata(dd->dai); struct audio_stream *stream; - int periods; - int samples; - int ret; - - comp_info(dev, "file_params()"); + struct comp_buffer *buffer; + struct comp_dev *dev = mod->dev; + struct file_comp_data *cd = module_get_private_data(mod); - ret = file_verify_params(dev, params); - if (ret < 0) { - comp_err(dev, "file_params(): pcm params verification failed."); - return ret; - } + debug_print("file_prepare()\n"); /* file component sink/source buffer period count */ + cd->max_frames = dev->frames; switch (cd->fs.mode) { case FILE_READ: buffer = list_first_item(&dev->bsink_list, struct comp_buffer, source_list); - periods = dev->ipc_config.periods_sink; break; case FILE_WRITE: buffer = list_first_item(&dev->bsource_list, struct comp_buffer, sink_list); - periods = dev->ipc_config.periods_source; break; default: /* TODO: duplex mode */ @@ -715,38 +728,16 @@ static int file_params(struct comp_dev *dev, return -EINVAL; } - /* set downstream buffer size */ + /* set file function */ stream = &buffer->stream; - samples = periods * dev->frames * audio_stream_get_channels(stream); switch (audio_stream_get_frm_fmt(stream)) { case SOF_IPC_FRAME_S16_LE: - ret = buffer_set_size(buffer, samples * sizeof(int16_t), 0); - if (ret < 0) { - fprintf(stderr, "error: file buffer size set\n"); - return ret; - } - - /* set file function */ cd->file_func = file_s16; break; case SOF_IPC_FRAME_S24_4LE: - ret = buffer_set_size(buffer, samples * sizeof(int32_t), 0); - if (ret < 0) { - fprintf(stderr, "error: file buffer size set\n"); - return ret; - } - - /* set file function */ cd->file_func = file_s24; break; case SOF_IPC_FRAME_S32_LE: - ret = buffer_set_size(buffer, samples * sizeof(int32_t), 0); - if (ret < 0) { - fprintf(stderr, "error: file buffer size set\n"); - return ret; - } - - /* set file function */ cd->file_func = file_s32; break; default: @@ -755,148 +746,31 @@ static int file_params(struct comp_dev *dev, return -EINVAL; } - cd->sample_container_bytes = audio_stream_sample_bytes(stream); - buffer_reset_pos(buffer, NULL); - return 0; } -static int fr_cmd(struct comp_dev *dev, struct sof_ipc_ctrl_data *cdata) +static int file_reset(struct processing_module *mod) { - fprintf(stderr, "Warning: Set data is not implemented\n"); - return -EINVAL; + struct file_comp_data *cd = module_get_private_data(mod); + + debug_print("file_reset()\n"); + cd->copies_timeout = 0; + return 0; } static int file_trigger(struct comp_dev *dev, int cmd) { - comp_info(dev, "file_trigger()"); + debug_print("file_trigger\n"); return comp_set_state(dev, cmd); } -/* used to pass standard and bespoke commands (with data) to component */ -static int file_cmd(struct comp_dev *dev, int cmd, void *data, - int max_data_size) -{ - struct sof_ipc_ctrl_data *cdata = ASSUME_ALIGNED(data, 4); - int ret = 0; - - comp_info(dev, "file_cmd()"); - switch (cmd) { - case COMP_CMD_SET_DATA: - ret = fr_cmd(dev, cdata); - break; - default: - fprintf(stderr, "Warning: Unknown file command %d\n", cmd); - return -EINVAL; - } - - return ret; -} - -/* - * copy and process stream samples - * returns the number of bytes copied - */ -static int file_copy(struct comp_dev *dev) -{ - struct comp_buffer *buffer; - struct dai_data *dd = comp_get_drvdata(dev); - struct file_comp_data *cd = comp_get_drvdata(dd->dai); - uint64_t cycles0, cycles1; - int snk_frames; - int src_frames; - int bytes = cd->sample_container_bytes; - int ret = 0; - - tb_getcycles(&cycles0); - switch (cd->fs.mode) { - case FILE_READ: - /* file component sink buffer */ - buffer = list_first_item(&dev->bsink_list, struct comp_buffer, - source_list); - - /* test sink has enough free frames */ - snk_frames = MIN(audio_stream_get_free_frames(&buffer->stream), dev->frames); - if (snk_frames > 0 && !cd->fs.reached_eof) { - /* read PCM samples from file */ - ret = cd->file_func(dev, &buffer->stream, NULL, - snk_frames); - - /* update sink buffer pointers */ - if (ret > 0) - comp_update_buffer_produce(buffer, - ret * bytes); - } - break; - case FILE_WRITE: - /* file component source buffer */ - buffer = list_first_item(&dev->bsource_list, - struct comp_buffer, sink_list); - - /* test source has enough free frames */ - src_frames = audio_stream_get_avail_frames(&buffer->stream); - if (src_frames > 0) { - /* write PCM samples into file */ - ret = cd->file_func(dev, NULL, &buffer->stream, - src_frames); - - /* update source buffer pointers */ - if (ret > 0) - comp_update_buffer_consume(buffer, - ret * bytes); - } - break; - default: - /* TODO: duplex mode */ - ret = -EINVAL; - break; - } - - cd->fs.copy_count++; - if (cd->fs.reached_eof || (cd->max_copies && cd->fs.copy_count >= cd->max_copies)) { - cd->fs.reached_eof = 1; - comp_info(dev, "file_copy(): copies %d max %d eof %d", - cd->fs.copy_count, cd->max_copies, - cd->fs.reached_eof); - schedule_task_cancel(dev->pipeline->pipe_task); - } - - tb_getcycles(&cycles1); - cd->fs.cycles_count += cycles1 - cycles0; - return ret; -} - -static int file_prepare(struct comp_dev *dev) -{ - int ret = 0; - - comp_info(dev, "file_prepare()"); - - ret = comp_set_state(dev, COMP_TRIGGER_PREPARE); - if (ret < 0) - return ret; - - if (ret == COMP_STATUS_STATE_ALREADY_SET) - return PPL_STATUS_PATH_STOP; - - dev->state = COMP_STATE_PREPARE; - return ret; -} - -static int file_reset(struct comp_dev *dev) -{ - comp_info(dev, "file_reset()"); - comp_set_state(dev, COMP_TRIGGER_RESET); - return 0; -} - static int file_get_hw_params(struct comp_dev *dev, struct sof_ipc_stream_params *params, int dir) { - struct dai_data *dd = comp_get_drvdata(dev); - struct file_comp_data *cd = comp_get_drvdata(dd->dai); + struct processing_module *mod = comp_get_drvdata(dev); + struct file_comp_data *cd = module_get_private_data(mod); - comp_info(dev, "file_hw_params()"); + debug_print("file_hw_params()\n"); params->direction = dir; params->rate = cd->rate; params->channels = cd->channels; @@ -905,50 +779,20 @@ static int file_get_hw_params(struct comp_dev *dev, return 0; } -static const struct comp_driver comp_file_host = { - .type = SOF_COMP_HOST, - .uid = SOF_RT_UUID(file_uuid), - .tctx = &file_tr, - .ops = { - .create = file_new, - .free = file_free, - .params = file_params, - .cmd = file_cmd, - .trigger = file_trigger, - .copy = file_copy, - .prepare = file_prepare, - .reset = file_reset, - }, - -}; - -static const struct comp_driver comp_file_dai = { - .type = SOF_COMP_DAI, - .uid = SOF_RT_UUID(file_uuid), - .tctx = &file_tr, - .ops = { - .create = file_new, - .free = file_free, - .params = file_params, - .cmd = file_cmd, - .trigger = file_trigger, - .copy = file_copy, - .prepare = file_prepare, - .reset = file_reset, - .dai_get_hw_params = file_get_hw_params, - }, +/* Needed for SOF_COMP_DAI */ +static struct module_endpoint_ops file_endpoint_ops = { + .dai_get_hw_params = file_get_hw_params, + .trigger = file_trigger, }; -static struct comp_driver_info comp_file_host_info = { - .drv = &comp_file_host, +static const struct module_interface file_interface = { + .init = file_init, + .prepare = file_prepare, + .process_audio_stream = file_process, + .reset = file_reset, + .free = file_free, + .endpoint_ops = &file_endpoint_ops, }; -static struct comp_driver_info comp_file_dai_info = { - .drv = &comp_file_dai, -}; - -void sys_comp_file_init(void) -{ - comp_register(&comp_file_host_info); - comp_register(&comp_file_dai_info); -} +DECLARE_MODULE_ADAPTER(file_interface, file_uuid, file_tr); +SOF_MODULE_INIT(file, sys_comp_module_file_interface_init); diff --git a/tools/testbench/include/testbench/common_test.h b/tools/testbench/include/testbench/common_test.h index dd43c29ab0eb..084195e17f04 100644 --- a/tools/testbench/include/testbench/common_test.h +++ b/tools/testbench/include/testbench/common_test.h @@ -12,10 +12,10 @@ #include <time.h> #include <stdio.h> #include <rtos/sof.h> +#include <tplg_parser/topology.h> #include <sof/audio/component_ext.h> #include <sof/math/numbers.h> #include <sof/audio/format.h> - #include <sof/lib/uuid.h> #define DEBUG_MSG_LEN 1024 @@ -23,11 +23,40 @@ #define MAX_INPUT_FILE_NUM 16 #define MAX_OUTPUT_FILE_NUM 16 +#define MAX_PIPELINES_NUM 16 + /* number of widgets types supported in testbench */ #define NUM_WIDGETS_SUPPORTED 16 -struct tplg_context; +#define TB_NAME_SIZE 256 + +#define TB_MAX_CONFIG 128 + +struct tb_mq_desc { + /* IPC message queue */ + //mqd_t mq; + //struct mq_attr attr; + char queue_name[TB_NAME_SIZE]; +}; + +struct tb_config { + char name[44]; + unsigned long buffer_frames; + unsigned long buffer_time; + unsigned long period_frames; + unsigned long period_time; + int rate; + int channels; + unsigned long format; +}; + +struct file_comp_lookup { + int id; + int instance_id; + int pipeline_id; + struct file_state *state; +}; /* * Global testbench data. @@ -37,20 +66,16 @@ struct tplg_context; */ struct testbench_prm { long long total_cycles; - char *tplg_file; /* topology file to use */ + int pipelines[MAX_PIPELINES_NUM]; + struct file_comp_lookup fr[MAX_INPUT_FILE_NUM]; + struct file_comp_lookup fw[MAX_OUTPUT_FILE_NUM]; char *input_file[MAX_INPUT_FILE_NUM]; /* input file names */ char *output_file[MAX_OUTPUT_FILE_NUM]; /* output file names */ + char *tplg_file; /* topology file to use */ + char *bits_in; /* input bit format */ int input_file_num; /* number of input files */ int output_file_num; /* number of output files */ - char *bits_in; /* input bit format */ - int pipelines[MAX_OUTPUT_FILE_NUM]; /* output file names */ int pipeline_num; - struct tplg_context *ctx; - - int fr_id; - int fw_id; - - int max_pipeline_id; int copy_iterations; bool copy_check; bool quiet; @@ -59,12 +84,12 @@ struct testbench_prm { int tick_period_us; int pipeline_duration_ms; int real_time; - FILE *file; + //FILE *file; char *pipeline_string; int output_file_index; int input_file_index; - struct tplg_comp_info *info; + //struct tplg_comp_info *info; int info_index; int info_elems; @@ -79,25 +104,39 @@ struct testbench_prm { uint32_t channels_in; uint32_t channels_out; enum sof_ipc_frame frame_fmt; + int ipc_version; + + /* topology */ + struct tplg_context tplg; + 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]; + int num_configs; + + size_t period_size; }; extern int debug; -int tb_parse_topology(struct testbench_prm *tb, struct tplg_context *ctx); +int tb_parse_topology(struct testbench_prm *tb); int edf_scheduler_init(void); -void sys_comp_file_init(void); - -void sys_comp_filewrite_init(void); - int tb_setup(struct sof *sof, struct testbench_prm *tp); void tb_free(struct sof *sof); int tb_pipeline_start(struct ipc *ipc, struct pipeline *p); -int tb_pipeline_params(struct testbench_prm *tp, struct ipc *ipc, struct pipeline *p, - struct tplg_context *ctx); +int tb_pipeline_params(struct testbench_prm *tp, struct ipc *ipc, struct pipeline *p); int tb_pipeline_stop(struct ipc *ipc, struct pipeline *p); @@ -109,4 +148,16 @@ void tb_gettime(struct timespec *td); void tb_getcycles(uint64_t *cycles); +int tb_load_topology(struct testbench_prm *tb); + +int tb_set_up_all_pipelines(struct testbench_prm *tb); + +void tb_show_file_stats(struct testbench_prm *tp, int pipeline_id); + +bool tb_schedule_pipeline_check_state(struct testbench_prm *tp); + +bool tb_is_pipeline_enabled(struct testbench_prm *tb, int pipeline_id); + +int tb_find_file_components(struct testbench_prm *tb); + #endif diff --git a/tools/testbench/include/testbench/file.h b/tools/testbench/include/testbench/file.h index d18f97972985..83ee9a222723 100644 --- a/tools/testbench/include/testbench/file.h +++ b/tools/testbench/include/testbench/file.h @@ -8,11 +8,13 @@ * Ranjani Sridharan <ranjani.sridharan@linux.intel.com> */ -#ifndef _FILE_H -#define _FILE_H +#ifndef _TESTBENCH_FILE_H +#define _TESTBENCH_FILE_H #include <stdint.h> +#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,8 +42,11 @@ struct file_state { enum file_format f_format; bool reached_eof; bool write_failed; + bool copy_timeout; }; +struct file_comp_data; + /* file comp data */ struct file_comp_data { struct file_state fs; @@ -49,12 +54,16 @@ struct file_comp_data { uint32_t channels; uint32_t rate; int sample_container_bytes; - int (*file_func)(struct comp_dev *dev, struct audio_stream *sink, + int (*file_func)(struct file_comp_data *cd, struct audio_stream *sink, struct audio_stream *source, uint32_t frames); /* maximum limits */ int max_samples; int max_copies; + int max_frames; + int copies_timeout; }; -#endif +void sys_comp_module_file_interface_init(void); + +#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 <ipc/topology.h> +#include <ipc4/base-config.h> + +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.h b/tools/testbench/include/testbench/topology.h new file mode 100644 index 000000000000..3091950cb236 --- /dev/null +++ b/tools/testbench/include/testbench/topology.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2024 Intel Corporation. All rights reserved. + */ + +#ifndef __TESTBENCH_TOPOLOGY_H_ +#define __TESTBENCH_TOPOLOGY_H_ + +#include "testbench/common_test.h" + +#define MAX_TPLG_OBJECT_SIZE 4096 +#define IPC3_MAX_MSG_SIZE 384 +#define IPC4_MAX_MSG_SIZE 384 + +int tb_new_aif_in_out(struct testbench_prm *tb, int dir); + +int tb_new_dai_in_out(struct testbench_prm *tb, int dir); + +int tb_new_pga(struct testbench_prm *tb); + +int tb_new_process(struct testbench_prm *tb); + +int tb_free_pipelines(struct testbench_prm *tb, int dir); + +void tb_free_topology(struct testbench_prm *tb); + +int tb_get_instance_id_from_pipeline_id(struct testbench_prm *tp, int id); + +int tb_set_up_pipelines(struct testbench_prm *tb, int dir); + +int tb_free_all_pipelines(struct testbench_prm *tb); + +#endif /* __TESTBENCH_TOPOLOGY_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..866750dccd75 --- /dev/null +++ b/tools/testbench/include/testbench/topology_ipc4.h @@ -0,0 +1,44 @@ +/* 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 <module/ipc4/base-config.h> +#include "testbench/common_test.h" + +#define TB_FAKE_IPC 0 + +int tb_parse_ipc4_comp_tokens(struct testbench_prm *tp, struct ipc4_base_module_cfg *base_cfg); + +void tb_setup_widget_ipc_msg(struct tplg_comp_info *comp_info); + +int tb_set_up_widget_ipc(struct testbench_prm *tb, struct tplg_comp_info *comp_info); + +int tb_set_up_route(struct testbench_prm *tb, struct tplg_route_info *route_info); + +int tb_set_up_pipeline(struct testbench_prm *tb, struct tplg_pipeline_info *pipe_info); + +void tb_pipeline_update_resource_usage(struct testbench_prm *tb, + struct tplg_comp_info *comp_info); + +int tb_is_single_format(struct sof_ipc4_pin_format *fmts, int num_formats); + +int tb_match_audio_format(struct testbench_prm *tb, struct tplg_comp_info *comp_info, + struct tb_config *config); + +int tb_set_up_widget_base_config(struct testbench_prm *tb, struct tplg_comp_info *comp_info); + +int tb_pipelines_set_state(struct testbench_prm *tb, int state, int dir); + +int tb_delete_pipeline(struct testbench_prm *tb, struct tplg_pipeline_info *pipe_info); + +int tb_free_route(struct testbench_prm *tb, struct tplg_route_info *route_info); + +int tb_set_running_state(struct testbench_prm *tb); + +int tb_set_reset_state(struct testbench_prm *tb); + +#endif /* _TESTBENCH_TOPOLOGY_IPC4_H */ diff --git a/tools/testbench/testbench.c b/tools/testbench/testbench.c index d4b03538792c..a3f26bc00db1 100644 --- a/tools/testbench/testbench.c +++ b/tools/testbench/testbench.c @@ -1,17 +1,19 @@ // SPDX-License-Identifier: BSD-3-Clause // -// Copyright(c) 2018 Intel Corporation. All rights reserved. +// Copyright(c) 2018-2024 Intel Corporation. All rights reserved. // // Author: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com> // Ranjani Sridharan <ranjani.sridharan@linux.intel.com> +#include <sof/audio/module_adapter/module/generic.h> #include <sof/ipc/driver.h> #include <sof/ipc/topology.h> -#include <platform/lib/ll_schedule.h> #include <sof/list.h> #include <getopt.h> #include "testbench/common_test.h" #include <tplg_parser/topology.h> +#include "testbench/topology_ipc4.h" +#include "testbench/topology.h" #include "testbench/trace.h" #include "testbench/file.h" #include <limits.h> @@ -141,140 +143,12 @@ static void print_usage(char *executable) printf("-b S16_LE -a volume=libsof_volume.so\n"); } -/* free components */ -static void test_pipeline_free_comps(int pipeline_id) -{ - struct list_item *clist; - struct list_item *temp; - struct ipc_comp_dev *icd = NULL; - int err; - - /* remove the components for this pipeline */ - list_for_item_safe(clist, temp, &sof_get()->ipc->comp_list) { - icd = container_of(clist, struct ipc_comp_dev, list); - - switch (icd->type) { - case COMP_TYPE_COMPONENT: - if (icd->cd->pipeline->pipeline_id != pipeline_id) - break; - err = ipc_comp_free(sof_get()->ipc, icd->id); - if (err) - fprintf(stderr, "failed to free comp %d\n", - icd->id); - break; - case COMP_TYPE_BUFFER: - if (buffer_pipeline_id(icd->cb) != pipeline_id) - break; - err = ipc_buffer_free(sof_get()->ipc, icd->id); - if (err) - fprintf(stderr, "failed to free buffer %d\n", - icd->id); - break; - default: - if (icd->pipeline->pipeline_id != pipeline_id) - break; - err = ipc_pipeline_free(sof_get()->ipc, icd->id); - if (err) - fprintf(stderr, "failed to free pipeline %d\n", - icd->id); - break; - } - } -} - -static void test_pipeline_set_test_limits(int pipeline_id, int max_copies, - int max_samples) -{ - struct list_item *clist; - struct list_item *temp; - struct ipc_comp_dev *icd = NULL; - struct comp_dev *cd; - struct dai_data *dd; - struct file_comp_data *fcd; - - /* set the test limits for this pipeline */ - list_for_item_safe(clist, temp, &sof_get()->ipc->comp_list) { - icd = container_of(clist, struct ipc_comp_dev, list); - - switch (icd->type) { - case COMP_TYPE_COMPONENT: - cd = icd->cd; - if (cd->pipeline->pipeline_id != pipeline_id) - break; - - switch (cd->drv->type) { - case SOF_COMP_HOST: - case SOF_COMP_DAI: - case SOF_COMP_FILEREAD: - case SOF_COMP_FILEWRITE: - /* only file limits supported today. TODO: add others */ - dd = comp_get_drvdata(cd); - fcd = comp_get_drvdata(dd->dai); - fcd->max_samples = max_samples; - fcd->max_copies = max_copies; - break; - default: - break; - } - break; - case COMP_TYPE_BUFFER: - default: - break; - } - } -} - -static void test_pipeline_get_file_stats(int pipeline_id) -{ - struct list_item *clist; - struct list_item *temp; - struct ipc_comp_dev *icd; - struct comp_dev *cd; - struct dai_data *dd; - struct file_comp_data *fcd; - unsigned long time; - - /* get the file IO status for each file in pipeline */ - list_for_item_safe(clist, temp, &sof_get()->ipc->comp_list) { - icd = container_of(clist, struct ipc_comp_dev, list); - - switch (icd->type) { - case COMP_TYPE_COMPONENT: - cd = icd->cd; - if (cd->pipeline->pipeline_id != pipeline_id) - break; - switch (cd->drv->type) { - case SOF_COMP_HOST: - case SOF_COMP_DAI: - case SOF_COMP_FILEREAD: - case SOF_COMP_FILEWRITE: - dd = comp_get_drvdata(cd); - fcd = comp_get_drvdata(dd->dai); - - time = cd->pipeline->pipe_task->start; - if (fcd->fs.copy_count == 0) - fcd->fs.copy_count = 1; - printf("file %s: id %d: type %d: samples %d copies %d total time %lu uS avg time %lu uS\n", - fcd->fs.fn, cd->ipc_config.id, cd->drv->type, fcd->fs.n, - fcd->fs.copy_count, time, time / fcd->fs.copy_count); - break; - default: - break; - } - break; - case COMP_TYPE_BUFFER: - default: - break; - } - } -} - static int parse_input_args(int argc, char **argv, struct testbench_prm *tp) { int option = 0; int ret = 0; - while ((option = getopt(argc, argv, "hdqi:o:t:b:a:r:R:c:n:C:P:Vp:T:D:")) != -1) { + while ((option = getopt(argc, argv, "hd:qi:o:t:b:r:R:c:n:C:P:p:T:D:")) != -1) { switch (option) { /* input sample file */ case 'i': @@ -370,232 +244,47 @@ static int parse_input_args(int argc, char **argv, struct testbench_prm *tp) return ret; } -static struct pipeline *get_pipeline_by_id(int id) -{ - struct ipc_comp_dev *pcm_dev; - struct ipc *ipc = sof_get()->ipc; - - pcm_dev = ipc_get_ppl_src_comp(ipc, id); - return pcm_dev->cd->pipeline; -} - -static int test_pipeline_stop(struct testbench_prm *tp) -{ - struct pipeline *p; - struct ipc *ipc = sof_get()->ipc; - int ret = 0; - int i; - - for (i = 0; i < tp->pipeline_num; i++) { - p = get_pipeline_by_id(tp->pipelines[i]); - ret = tb_pipeline_stop(ipc, p); - if (ret < 0) - break; - } - - return ret; -} - -static int test_pipeline_reset(struct testbench_prm *tp) -{ - struct pipeline *p; - struct ipc *ipc = sof_get()->ipc; - int ret = 0; - int i; - - for (i = 0; i < tp->pipeline_num; i++) { - p = get_pipeline_by_id(tp->pipelines[i]); - ret = tb_pipeline_reset(ipc, p); - if (ret < 0) - break; - } - - return ret; -} - -static void test_pipeline_free(struct testbench_prm *tp) -{ - int i; - - for (i = 0; i < tp->pipeline_num; i++) - test_pipeline_free_comps(tp->pipelines[i]); -} - -static int test_pipeline_params(struct testbench_prm *tp, struct tplg_context *ctx) -{ - struct ipc_comp_dev *pcm_dev; - struct pipeline *p; - struct ipc *ipc = sof_get()->ipc; - int ret = 0; - int i; - - /* Run pipeline until EOF from fileread */ - - for (i = 0; i < tp->pipeline_num; i++) { - pcm_dev = ipc_get_ppl_src_comp(ipc, tp->pipelines[i]); - if (!pcm_dev) { - fprintf(stderr, "error: pipeline %d has no source component\n", - tp->pipelines[i]); - return -EINVAL; - } - - /* set up pipeline params */ - p = pcm_dev->cd->pipeline; - - /* input and output sample rate */ - if (!tp->fs_in) - tp->fs_in = p->period * p->frames_per_sched; - - if (!tp->fs_out) - tp->fs_out = p->period * p->frames_per_sched; - - ret = tb_pipeline_params(tp, ipc, p, ctx); - if (ret < 0) { - fprintf(stderr, "error: pipeline params failed: %s\n", - strerror(ret)); - return ret; - } - } - - - return 0; -} - -static int test_pipeline_start(struct testbench_prm *tp) -{ - struct pipeline *p; - struct ipc *ipc = sof_get()->ipc; - int i; - - /* Run pipeline until EOF from fileread */ - for (i = 0; i < tp->pipeline_num; i++) { - p = get_pipeline_by_id(tp->pipelines[i]); - - /* do we need to apply copy count limit ? */ - if (tp->copy_check) - test_pipeline_set_test_limits(tp->pipelines[i], tp->copy_iterations, 0); - - /* set pipeline params and trigger start */ - if (tb_pipeline_start(ipc, p) < 0) { - fprintf(stderr, "error: pipeline params\n"); - return -EINVAL; - } - } - - return 0; -} - -static bool test_pipeline_check_state(struct testbench_prm *tp, int state) -{ - struct pipeline *p; - uint64_t cycles0, cycles1; - int i; - - tb_getcycles(&cycles0); - - schedule_ll_run_tasks(); - - tb_getcycles(&cycles1); - tp->total_cycles += cycles1 - cycles0; - - /* Run pipeline until EOF from fileread */ - for (i = 0; i < tp->pipeline_num; i++) { - p = get_pipeline_by_id(tp->pipelines[i]); - if (p->pipe_task->state == state) - return true; - } - - return false; -} - -static int test_pipeline_load(struct testbench_prm *tp, struct tplg_context *ctx) -{ - int ret; - - /* setup the thread virtual core config */ - memset(ctx, 0, sizeof(*ctx)); - ctx->comp_id = 1; - ctx->core_id = 0; - ctx->sof = sof_get(); - ctx->tplg_file = tp->tplg_file; - ctx->ipc_major = 3; - - /* parse topology file and create pipeline */ - ret = tb_parse_topology(tp, ctx); - if (ret < 0) - fprintf(stderr, "error: parsing topology\n"); - - return ret; -} - -static void test_pipeline_stats(struct testbench_prm *tp, - struct tplg_context *ctx, long long delta_t) +static void test_pipeline_stats(struct testbench_prm *tp, long long delta_t) { - int count = 1; - struct ipc_comp_dev *icd; - struct comp_dev *cd; - struct dai_data *dd; - struct pipeline *p; - struct file_comp_data *frcd, *fwcd; long long file_cycles, pipeline_cycles; float pipeline_mcps; int n_in, n_out, frames_out; int i; + int count = 1; - /* Get pointer to filewrite */ - icd = ipc_get_comp_by_id(sof_get()->ipc, tp->fw_id); - if (!icd) { - fprintf(stderr, "error: failed to get pointers to filewrite\n"); - exit(EXIT_FAILURE); - } - cd = icd->cd; - dd = comp_get_drvdata(cd); - fwcd = comp_get_drvdata(dd->dai); - - /* Get pointer to fileread */ - icd = ipc_get_comp_by_id(sof_get()->ipc, tp->fr_id); - if (!icd) { - fprintf(stderr, "error: failed to get pointers to fileread\n"); - exit(EXIT_FAILURE); - } - cd = icd->cd; - dd = comp_get_drvdata(cd); - frcd = comp_get_drvdata(dd->dai); - - /* Run pipeline until EOF from fileread */ - icd = ipc_get_comp_by_id(sof_get()->ipc, ctx->sched_id); - p = icd->cd->pipeline; + n_out = 0; + n_in = 0; + file_cycles = 0; + for (i = 0; i < tp->input_file_num; i++) { + if (tp->fr[i].id < 0) + continue; - /* input and output sample rate */ - if (!tp->fs_in) - tp->fs_in = p->period * p->frames_per_sched; + n_in += tp->fr[i].state->n; + file_cycles += tp->fr[i].state->cycles_count; + } - if (!tp->fs_out) - tp->fs_out = p->period * p->frames_per_sched; + for (i = 0; i < tp->output_file_num; i++) { + if (tp->fw[i].id < 0) + continue; - n_in = frcd->fs.n; - n_out = fwcd->fs.n; - file_cycles = frcd->fs.cycles_count + fwcd->fs.cycles_count; + n_out += tp->fw[i].state->n; + file_cycles += tp->fw[i].state->cycles_count; + } /* print test summary */ printf("==========================================================\n"); printf(" Test Summary %d\n", count); printf("==========================================================\n"); - printf("Test Pipeline:\n"); - printf("%s\n", tp->pipeline_string); - test_pipeline_get_file_stats(ctx->pipeline_id); + + for (i = 0; i < tp->pipeline_num; i++) { + printf("pipeline %d\n", tp->pipelines[i]); + tb_show_file_stats(tp, tp->pipelines[i]); + } printf("Input bit format: %s\n", tp->bits_in); printf("Input sample rate: %d\n", tp->fs_in); printf("Output sample rate: %d\n", tp->fs_out); - for (i = 0; i < tp->input_file_num; i++) { - printf("Input[%d] read from file: \"%s\"\n", - i, tp->input_file[i]); - } - for (i = 0; i < tp->output_file_num; i++) { - printf("Output[%d] written to file: \"%s\"\n", - i, tp->output_file[i]); - } + frames_out = n_out / tp->channels_out; printf("Input sample (frame) count: %d (%d)\n", n_in, n_in / tp->channels_in); printf("Output sample (frame) count: %d (%d)\n", n_out, frames_out); @@ -624,7 +313,6 @@ static void test_pipeline_stats(struct testbench_prm *tp, static int pipline_test(struct testbench_prm *tp) { int dp_count = 0; - struct tplg_context ctx; struct timespec ts; struct timespec td0, td1; long long delta_t; @@ -642,24 +330,28 @@ static int pipline_test(struct testbench_prm *tp) printf(" Test Start %d\n", dp_count); printf("==========================================================\n"); - err = test_pipeline_load(tp, &ctx); + err = tb_load_topology(tp); if (err < 0) { - fprintf(stderr, "error: pipeline load %d failed %d\n", - dp_count, err); + fprintf(stderr, "error: topology load %d failed %d\n", dp_count, err); break; } - err = test_pipeline_params(tp, &ctx); + err = tb_set_up_all_pipelines(tp); if (err < 0) { - fprintf(stderr, "error: pipeline params %d failed %d\n", - dp_count, err); + fprintf(stderr, "error: pipelines set up %d failed %d\n", dp_count, err); break; } - err = test_pipeline_start(tp); + err = tb_set_running_state(tp); if (err < 0) { - fprintf(stderr, "error: pipeline run %d failed %d\n", - dp_count, err); + fprintf(stderr, "error: pipelines state set %d failed %d\n", dp_count, err); + break; + } + + + err = tb_find_file_components(tp); /* Track file comp status during copying */ + if (err < 0) { + fprintf(stderr, "error: file component find failed %d\n", err); break; } @@ -686,10 +378,8 @@ static int pipline_test(struct testbench_prm *tp) #endif if (err == 0) { nsleep_time += tp->tick_period_us; /* sleep fully completed */ - if (test_pipeline_check_state(tp, SOF_TASK_STATE_CANCEL)) { - fprintf(stdout, "pipeline cancelled !\n"); + if (tb_schedule_pipeline_check_state(tp)) break; - } } else { if (err == EINTR) { continue; /* interrupted - keep going */ @@ -700,38 +390,40 @@ static int pipline_test(struct testbench_prm *tp) } } + tb_schedule_pipeline_check_state(tp); /* Once more to flush out remaining data */ + tb_gettime(&td1); - err = test_pipeline_stop(tp); + err = tb_set_reset_state(tp); if (err < 0) { - fprintf(stderr, "error: pipeline stop %d failed %d\n", + fprintf(stderr, "error: pipeline reset %d failed %d\n", dp_count, err); break; } + /* TODO: This should be printed after reset and free to get cleaner output + * but the file internal status would be lost there. + */ delta_t = (td1.tv_sec - td0.tv_sec) * 1000000; delta_t += (td1.tv_nsec - td0.tv_nsec) / 1000; - test_pipeline_stats(tp, &ctx, delta_t); + test_pipeline_stats(tp, delta_t); - err = test_pipeline_reset(tp); + err = tb_free_all_pipelines(tp); if (err < 0) { - fprintf(stderr, "error: pipeline stop %d failed %d\n", - dp_count, err); + fprintf(stderr, "error: free pipelines %d failed %d\n", dp_count, err); break; } - test_pipeline_free(tp); - + tb_free_topology(tp); dp_count++; } return 0; } -static struct testbench_prm tp; - int main(int argc, char **argv) { + struct testbench_prm tp; int i, err; /* initialize input and output sample rates, files, etc. */ @@ -742,6 +434,8 @@ int main(int argc, char **argv) tp.tplg_file = NULL; tp.input_file_num = 0; tp.output_file_num = 0; + tp.input_file_index = 0; + tp.output_file_index = 0; for (i = 0; i < MAX_OUTPUT_FILE_NUM; i++) tp.output_file[i] = NULL; @@ -750,7 +444,6 @@ int main(int argc, char **argv) tp.channels_in = TESTBENCH_NCH; tp.channels_out = 0; - tp.max_pipeline_id = 0; tp.copy_check = false; tp.quiet = 0; tp.dynamic_pipeline_iterations = 1; @@ -760,6 +453,21 @@ int main(int argc, char **argv) tp.tick_period_us = 0; /* Execute fast non-real time, for 1 ms tick use -T 1000 */ tp.pipeline_duration_ms = 5000; tp.copy_iterations = 1; + tp.ipc_version = 4; // FIXME from IPC_CONVFIG_MAJOR + tp.period_size = 96; // FIXME becomes somehow obs in tb_match_audio_format() + + // TODO move somewhere else and integrate with command line + tp.num_configs = 1; + strcpy(tp.config[0].name, "48k2c32b"); + tp.config[0].buffer_frames = 24000; + tp.config[0].buffer_time = 0; + tp.config[0].period_frames = 6000; + tp.config[0].period_time = 0; + tp.config[0].rate = 48000; + tp.config[0].channels = 2; + tp.config[0].format = SOF_IPC_FRAME_S32_LE; + + tp.pcm_id = 0; // TODO: Fix this /* command line arguments*/ err = parse_input_args(argc, argv, &tp); diff --git a/tools/testbench/topology.c b/tools/testbench/topology.c index 95e1450df693..2e69ef329f1e 100644 --- a/tools/testbench/topology.c +++ b/tools/testbench/topology.c @@ -1,554 +1,321 @@ // SPDX-License-Identifier: BSD-3-Clause // -// Copyright(c) 2018 Intel Corporation. All rights reserved. +// Copyright(c) 2018-2024 Intel Corporation. All rights reserved. // // Author: Ranjani Sridharan <ranjani.sridharan@linux.intel.com> // Liam Girdwood <liam.r.girdwood@linux.intel.com> /* Topology loader to set up components and pipeline */ -#include <errno.h> -#include <math.h> -#include <stdio.h> -#include <stdlib.h> -#include <sof/common.h> -#include <rtos/string.h> #include <sof/audio/component.h> #include <sof/ipc/driver.h> #include <sof/ipc/topology.h> -#include <tplg_parser/topology.h> +#include <rtos/string.h> +#include <sof/common.h> +#include <sof/lib/uuid.h> #include <tplg_parser/tokens.h> +#include <tplg_parser/topology.h> +#include <errno.h> +#include <math.h> +#include <stdio.h> +#include <stdlib.h> #include "testbench/common_test.h" #include "testbench/file.h" +#include "testbench/topology.h" +#include "testbench/topology_ipc4.h" -#define MAX_TPLG_OBJECT_SIZE 4096 - -/* load asrc dapm widget */ -static int tb_register_asrc(struct testbench_prm *tp, struct tplg_context *ctx) +static int tb_new_src(struct testbench_prm *tb) { + struct tplg_context *ctx = &tb->tplg; char tplg_object[MAX_TPLG_OBJECT_SIZE] = {0}; - struct sof_ipc_comp *comp = (struct sof_ipc_comp *)tplg_object; - struct sof *sof = ctx->sof; - struct sof_ipc_comp_asrc *asrc; - int ret = 0; - - ret = tplg_new_asrc(ctx, comp, MAX_TPLG_OBJECT_SIZE, NULL, 0); - if (ret < 0) - return ret; - - asrc = (struct sof_ipc_comp_asrc *)comp; - - /* set testbench input and output sample rate from topology */ - if (!tp->fs_out) - tp->fs_out = asrc->sink_rate; - else - asrc->sink_rate = tp->fs_out; - - if (!tp->fs_in) - tp->fs_in = asrc->source_rate; - else - asrc->source_rate = tp->fs_in; - - /* load asrc component */ - if (ipc_comp_new(sof->ipc, ipc_to_comp_new(asrc)) < 0) { - fprintf(stderr, "error: new asrc comp\n"); - return -EINVAL; - } - - return ret; -} - -/* load buffer DAPM widget */ -static int tb_register_buffer(struct testbench_prm *tp, struct tplg_context *ctx) -{ - struct sof *sof = ctx->sof; - struct sof_ipc_buffer buffer = {{{0}}}; + struct sof_ipc_comp_src *src = (struct sof_ipc_comp_src *)tplg_object; + struct snd_soc_tplg_ctl_hdr *tplg_ctl; int ret; - ret = tplg_new_buffer(ctx, &buffer, sizeof(buffer), - NULL, 0); - if (ret < 0) - return ret; - - /* create buffer component */ - if (ipc_buffer_new(sof->ipc, &buffer) < 0) { - fprintf(stderr, "error: buffer new\n"); - return -EINVAL; - } - - return 0; -} - -/* load pipeline graph DAPM widget*/ -static int tb_register_graph(struct tplg_context *ctx, struct tplg_comp_info *temp_comp_list, - char *pipeline_string, - int num_comps, int num_connections, - int pipeline_id) -{ - struct sof_ipc_pipe_comp_connect connection; - struct sof *sof = ctx->sof; - int ret = 0; - int i; - - for (i = 0; i < num_connections; i++) { - ret = tplg_create_graph(ctx, num_comps, pipeline_id, temp_comp_list, - pipeline_string, &connection, i); - if (ret < 0) - return ret; - - /* connect source and sink */ - if (ipc_comp_connect(sof->ipc, ipc_to_pipe_connect(&connection)) < 0) { - fprintf(stderr, "error: comp connect\n"); - return -EINVAL; - } - } + tplg_ctl = calloc(ctx->hdr->payload_size, 1); + if (!tplg_ctl) + return -ENOMEM; - /* pipeline complete after pipeline connections are established */ - for (i = 0; i < num_comps; i++) { - if (temp_comp_list[i].pipeline_id == pipeline_id && - temp_comp_list[i].type == SND_SOC_TPLG_DAPM_SCHEDULER) - ipc_pipeline_complete(sof->ipc, temp_comp_list[i].id); + ret = tplg_new_src(ctx, &src->comp, 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; } -/* load mixer dapm widget */ -static int tb_register_mixer(struct testbench_prm *tp, struct tplg_context *ctx) +static int tb_new_asrc(struct testbench_prm *tb) { + struct tplg_context *ctx = &tb->tplg; char tplg_object[MAX_TPLG_OBJECT_SIZE] = {0}; - struct sof_ipc_comp *comp = (struct sof_ipc_comp *)tplg_object; - struct sof *sof = ctx->sof; - int ret = 0; + struct sof_ipc_comp_asrc *asrc = (struct sof_ipc_comp_asrc *)tplg_object; + struct snd_soc_tplg_ctl_hdr *tplg_ctl; + int ret; - ret = tplg_new_mixer(ctx, comp, MAX_TPLG_OBJECT_SIZE, NULL, 0); - if (ret < 0) - return ret; + tplg_ctl = calloc(ctx->hdr->payload_size, 1); + if (!tplg_ctl) + return -ENOMEM; - /* load mixer component */ - if (ipc_comp_new(sof->ipc, ipc_to_comp_new(comp)) < 0) { - fprintf(stderr, "error: new mixer comp\n"); - ret = -EINVAL; + ret = tplg_new_asrc(ctx, &asrc->comp, 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_register_pga(struct testbench_prm *tp, struct tplg_context *ctx) +static int tb_new_mixer(struct testbench_prm *tb) { + struct tplg_context *ctx = &tb->tplg; + struct tplg_comp_info *comp_info = ctx->current_comp_info; char tplg_object[MAX_TPLG_OBJECT_SIZE] = {0}; - struct sof_ipc_comp *comp = (struct sof_ipc_comp *)tplg_object; - struct sof *sof = ctx->sof; + struct sof_ipc_comp_mixer *mixer = (struct sof_ipc_comp_mixer *)tplg_object; + struct snd_soc_tplg_ctl_hdr *tplg_ctl; int ret; - ret = tplg_new_pga(ctx, comp, MAX_TPLG_OBJECT_SIZE, NULL, 0); + tplg_ctl = calloc(ctx->hdr->payload_size, 1); + if (!tplg_ctl) + return -ENOMEM; + + comp_info->instance_id = tb->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, MAX_TPLG_OBJECT_SIZE, + tplg_ctl, ctx->hdr->payload_size); if (ret < 0) { - fprintf(stderr, "error: failed to create PGA\n"); - return ret; + fprintf(stderr, "error: failed to create mixer\n"); + goto out; } - /* load volume component */ - if (ipc_comp_new(sof->ipc, ipc_to_comp_new(comp)) < 0) { - fprintf(stderr, "error: new pga comp\n"); - ret = -EINVAL; + 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; } -/* load scheduler dapm widget */ -static int tb_register_pipeline(struct testbench_prm *tp, struct tplg_context *ctx) +static int tb_new_pipeline(struct testbench_prm *tb) { - struct sof *sof = ctx->sof; - struct sof_ipc_pipe_new pipeline = {{0}}; + 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 = &tb->tplg; int ret; - ret = tplg_new_pipeline(ctx, &pipeline, sizeof(pipeline), NULL); - if (ret < 0) - return ret; - - pipeline.sched_id = ctx->sched_id; + tplg_ctl = calloc(ctx->hdr->payload_size, 1); + if (!tplg_ctl) + return -ENOMEM; - /* Create pipeline */ - if (ipc_pipeline_new(sof->ipc, (ipc_pipe_new *)&pipeline) < 0) { - fprintf(stderr, "error: pipeline new\n"); - return -EINVAL; + pipe_info = calloc(sizeof(struct tplg_pipeline_info), 1); + if (!pipe_info) { + ret = -ENOMEM; + goto out; } - return 0; -} - -/* load process dapm widget */ -static int tb_register_process(struct testbench_prm *tp, struct tplg_context *ctx) -{ - struct sof *sof = ctx->sof; - struct sof_ipc_comp_process *process; - int ret = 0; + pipe_info->name = strdup(ctx->widget->name); + if (!pipe_info->name) { + free(pipe_info); + goto out; + } - process = calloc(1, MAX_TPLG_OBJECT_SIZE); - if (!process) - return -ENOMEM; + pipe_info->id = ctx->pipeline_id; - ret = tplg_new_process(ctx, process, MAX_TPLG_OBJECT_SIZE, NULL, 0); - if (ret < 0) + 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; + } - /* Instantiate */ - ret = ipc_comp_new(sof->ipc, ipc_to_comp_new(process)); - + list_item_append(&pipe_info->item, &tb->pipeline_list); + tplg_debug("loading pipeline %s\n", pipe_info->name); out: - free(process); - if (ret < 0) - fprintf(stderr, "error: new process comp\n"); - + free(tplg_ctl); return ret; } -/* load src dapm widget */ -static int tb_register_src(struct testbench_prm *tp, struct tplg_context *ctx) +static int tb_new_buffer(struct testbench_prm *tb) { - struct sof *sof = ctx->sof; - char tplg_object[MAX_TPLG_OBJECT_SIZE] = {0}; - struct sof_ipc_comp *comp = (struct sof_ipc_comp *)tplg_object; - struct sof_ipc_comp_src *src; - int ret = 0; - - ret = tplg_new_src(ctx, comp, MAX_TPLG_OBJECT_SIZE, NULL, 0); - if (ret < 0) - return ret; - - src = (struct sof_ipc_comp_src *)comp; + struct ipc4_copier_module_cfg *copier = calloc(sizeof(struct ipc4_copier_module_cfg), 1); + struct tplg_context *ctx = &tb->tplg; + struct tplg_comp_info *comp_info = ctx->current_comp_info; + int ret; - /* set testbench input and output sample rate from topology */ - if (!tp->fs_out) - tp->fs_out = src->sink_rate; - else - src->sink_rate = tp->fs_out; + if (!copier) + return -ENOMEM; - if (!tp->fs_in) - tp->fs_in = src->source_rate; - else - src->source_rate = tp->fs_in; + comp_info->ipc_payload = copier; - /* load src component */ - if (ipc_comp_new(sof->ipc, ipc_to_comp_new(comp)) < 0) { - fprintf(stderr, "error: new src comp\n"); - return -EINVAL; + 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; } -/* load fileread component */ -static int tb_new_fileread(struct tplg_context *ctx, - struct sof_ipc_comp_file *fileread) +static int tb_register_graph(struct testbench_prm *tb, int count) { - struct snd_soc_tplg_vendor_array *array = &ctx->widget->priv.array[0]; - size_t total_array_size = 0; - int size = ctx->widget->priv.size; - int comp_id = ctx->comp_id; - char uuid[UUID_SIZE]; - int ret; - - /* read vendor tokens */ - while (total_array_size < size) { - if (!tplg_is_valid_priv_size(total_array_size, size, array)) { - fprintf(stderr, "error: filewrite array size mismatch for widget size %d\n", - size); - return -EINVAL; - } - - /* parse comp tokens */ - ret = sof_parse_tokens(&fileread->config, comp_tokens, - ARRAY_SIZE(comp_tokens), array, - array->size); - if (ret != 0) { - fprintf(stderr, "error: parse comp tokens %d\n", - size); - return -EINVAL; - } - - /* parse uuid token */ - ret = sof_parse_tokens(uuid, comp_ext_tokens, - ARRAY_SIZE(comp_ext_tokens), array, - array->size); - if (ret != 0) { - fprintf(stderr, "error: parse mixer uuid token %d\n", size); - return -EINVAL; - } + struct tplg_context *ctx = &tb->tplg; + int ret = 0; + int i; - total_array_size += array->size; - array = MOVE_POINTER_BY_BYTES(array, array->size); + for (i = 0; i < count; i++) { + ret = tplg_parse_graph(ctx, &tb->widget_list, &tb->route_list); + if (ret < 0) + return ret; } - /* configure fileread */ - fileread->mode = FILE_READ; - fileread->comp.id = comp_id; - - /* use fileread comp as scheduling comp */ - fileread->comp.core = ctx->core_id; - fileread->comp.hdr.size = sizeof(struct sof_ipc_comp_file); - fileread->comp.type = SOF_COMP_FILEREAD; - fileread->comp.pipeline_id = ctx->pipeline_id; - fileread->config.hdr.size = sizeof(struct sof_ipc_comp_config); - return 0; + return ret; } -/* load filewrite component */ -static int tb_new_filewrite(struct tplg_context *ctx, - struct sof_ipc_comp_file *filewrite) +static int tb_parse_pcm(struct testbench_prm *tb, int count) { - struct snd_soc_tplg_vendor_array *array = &ctx->widget->priv.array[0]; - size_t total_array_size = 0; - int size = ctx->widget->priv.size; - int comp_id = ctx->comp_id; - char uuid[UUID_SIZE]; - int ret; - - /* read vendor tokens */ - while (total_array_size < size) { - if (!tplg_is_valid_priv_size(total_array_size, size, array)) { - fprintf(stderr, "error: filewrite array size mismatch\n"); - return -EINVAL; - } + struct tplg_context *ctx = &tb->tplg; + int ret, i; - ret = sof_parse_tokens(&filewrite->config, comp_tokens, - ARRAY_SIZE(comp_tokens), array, - array->size); - if (ret != 0) { - fprintf(stderr, "error: parse filewrite tokens %d\n", - size); - return -EINVAL; - } - - /* parse uuid token */ - ret = sof_parse_tokens(uuid, comp_ext_tokens, - ARRAY_SIZE(comp_ext_tokens), array, - array->size); - if (ret != 0) { - fprintf(stderr, "error: parse mixer uuid token %d\n", size); - return -EINVAL; - } - - total_array_size += array->size; - array = MOVE_POINTER_BY_BYTES(array, array->size); + for (i = 0; i < count; i++) { + ret = tplg_parse_pcm(ctx, &tb->widget_list, &tb->pcm_list); + if (ret < 0) + return ret; } - /* configure filewrite */ - filewrite->comp.core = ctx->core_id; - filewrite->comp.id = comp_id; - filewrite->mode = FILE_WRITE; - filewrite->comp.hdr.size = sizeof(struct sof_ipc_comp_file); - filewrite->comp.type = SOF_COMP_FILEWRITE; - filewrite->comp.pipeline_id = ctx->pipeline_id; - filewrite->config.hdr.size = sizeof(struct sof_ipc_comp_config); return 0; } -/* load fileread component */ -static int tb_register_fileread(struct testbench_prm *tp, - struct tplg_context *ctx, int dir) +/* + * 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 sof *sof = ctx->sof; - struct sof_ipc_comp_file fileread = {{{0}}}; + struct tplg_context *ctx = &tp->tplg; + struct tplg_comp_info *comp_info; + int comp_id = ctx->comp_id; int ret; - fileread.config.frame_fmt = tplg_find_format(tp->bits_in); + if (ctx->widget->id == SND_SOC_TPLG_DAPM_SCHEDULER) + return 0; - ret = tb_new_fileread(ctx, &fileread); - if (ret < 0) - return ret; + comp_info = calloc(sizeof(struct tplg_comp_info), 1); + if (!comp_info) + return -ENOMEM; - /* configure fileread */ - fileread.fn = strdup(tp->input_file[tp->input_file_index]); - if (tp->input_file_index == 0) - tp->fr_id = ctx->comp_id; - - /* use fileread comp as scheduling comp */ - ctx->sched_id = ctx->comp_id; - tp->input_file_index++; - - /* Set format from testbench command line*/ - fileread.rate = tp->fs_in; - fileread.channels = tp->channels_in; - fileread.frame_fmt = tp->frame_fmt; - fileread.direction = dir; - - /* Set type depending on direction */ - fileread.comp.type = (dir == SOF_IPC_STREAM_PLAYBACK) ? - SOF_COMP_HOST : SOF_COMP_DAI; - - /* create fileread component */ - if (ipc_comp_new(sof->ipc, ipc_to_comp_new(&fileread)) < 0) { - fprintf(stderr, "error: file read\n"); - free(fileread.fn); - return -EINVAL; + comp_info->name = strdup(ctx->widget->name); + if (!comp_info->name) { + ret = -ENOMEM; + goto err; } - free(fileread.fn); - return 0; -} - -/* load filewrite component */ -static int tb_register_filewrite(struct testbench_prm *tp, - struct tplg_context *ctx, int dir) -{ - struct sof *sof = ctx->sof; - struct sof_ipc_comp_file filewrite = {{{0}}}; - int ret; - - ret = tb_new_filewrite(ctx, &filewrite); - if (ret < 0) - return ret; - - /* configure filewrite (multiple output files are supported.) */ - if (!tp->output_file[tp->output_file_index]) { - fprintf(stderr, "error: output[%d] file name is null\n", - tp->output_file_index); - return -EINVAL; - } - filewrite.fn = strdup(tp->output_file[tp->output_file_index]); - if (tp->output_file_index == 0) - tp->fw_id = ctx->comp_id; - tp->output_file_index++; - - /* Set format from testbench command line*/ - filewrite.rate = tp->fs_out; - filewrite.channels = tp->channels_out; - filewrite.frame_fmt = tp->frame_fmt; - filewrite.direction = dir; - - /* Set type depending on direction */ - filewrite.comp.type = (dir == SOF_IPC_STREAM_PLAYBACK) ? - SOF_COMP_DAI : SOF_COMP_HOST; - - /* create filewrite component */ - if (ipc_comp_new(sof->ipc, ipc_to_comp_new(&filewrite)) < 0) { - fprintf(stderr, "error: new file write\n"); - free(filewrite.fn); - return -EINVAL; + comp_info->stream_name = strdup(ctx->widget->sname); + if (!comp_info->stream_name) { + ret = -ENOMEM; + goto sname_err; } - free(filewrite.fn); - return 0; -} - -static int tb_register_aif_in_out(struct testbench_prm *tb, - struct tplg_context *ctx, int dir) -{ - if (dir == SOF_IPC_STREAM_PLAYBACK) - return tb_register_fileread(tb, ctx, dir); - else - return tb_register_filewrite(tb, ctx, dir); -} - -static int tb_register_dai_in_out(struct testbench_prm *tb, - struct tplg_context *ctx, int dir) -{ - if (dir == SOF_IPC_STREAM_PLAYBACK) - return tb_register_filewrite(tb, ctx, dir); - else - return tb_register_fileread(tb, ctx, dir); -} - -/* - * 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 *tb, struct tplg_context *ctx) -{ - struct tplg_comp_info *temp_comp_list = tb->info; - int comp_index = tb->info_index; - int comp_id = ctx->comp_id; + comp_info->id = comp_id; + comp_info->type = ctx->widget->id; + comp_info->pipeline_id = ctx->pipeline_id; + ctx->current_comp_info = comp_info; - /* mapping should be empty */ - if (temp_comp_list[comp_index].name) { - fprintf(stderr, "comp index %d already in use with %d:%s cant insert %d:%s\n", - comp_index, - temp_comp_list[comp_index].id, temp_comp_list[comp_index].name, - ctx->widget->id, ctx->widget->name); - return -EINVAL; + // TODO IPC3 + if (ctx->ipc_major == 4) { + ret = tb_parse_ipc4_comp_tokens(tp, &comp_info->basecfg); + if (ret < 0) + goto sname_err; } - temp_comp_list[comp_index].id = comp_id; - temp_comp_list[comp_index].name = ctx->widget->name; - temp_comp_list[comp_index].type = ctx->widget->id; - temp_comp_list[comp_index].pipeline_id = ctx->pipeline_id; + list_item_append(&comp_info->item, &tp->widget_list); - printf("debug: loading idx %d comp_id %d: widget %s type %d size %d at offset %ld\n", - comp_index, comp_id, ctx->widget->name, ctx->widget->id, ctx->widget->size, - ctx->tplg_offset); + 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 *tb, struct tplg_context *ctx) +static int tb_load_widget(struct testbench_prm *tb) { - struct tplg_comp_info *temp_comp_list = tb->info; - int comp_id = ctx->comp_id; + struct tplg_context *ctx = &tb->tplg; int ret = 0; /* get next widget */ ctx->widget = tplg_get_widget(ctx); ctx->widget_size = ctx->widget->size; - if (!temp_comp_list) { - fprintf(stderr, "load_widget: temp_comp_list argument NULL\n"); - return -EINVAL; - } - /* insert widget into mapping */ - ret = tb_insert_comp(tb, ctx); + ret = tb_insert_comp(tb); if (ret < 0) { - fprintf(stderr, "plug_load_widget: invalid widget index\n"); + fprintf(stderr, "tb_load_widget: invalid widget index\n"); return ret; } - printf("debug: loading comp_id %d: widget %s id %d\n", - comp_id, ctx->widget->name, ctx->widget->id); - /* load widget based on type */ - switch (ctx->widget->id) { + switch (tb->tplg.widget->id) { /* load pga widget */ case SND_SOC_TPLG_DAPM_PGA: - if (tb_register_pga(tb, ctx) < 0) { + if (tb_new_pga(tb) < 0) { fprintf(stderr, "error: load pga\n"); ret = -EINVAL; goto exit; } break; case SND_SOC_TPLG_DAPM_AIF_IN: - if (tb_register_aif_in_out(tb, ctx, SOF_IPC_STREAM_PLAYBACK) < 0) { + if (tb_new_aif_in_out(tb, 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_register_aif_in_out(tb, ctx, SOF_IPC_STREAM_CAPTURE) < 0) { + if (tb_new_aif_in_out(tb, 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_register_dai_in_out(tb, ctx, SOF_IPC_STREAM_PLAYBACK) < 0) { + if (tb_new_dai_in_out(tb, 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_register_dai_in_out(tb, ctx, SOF_IPC_STREAM_CAPTURE) < 0) { + if (tb_new_dai_in_out(tb, SOF_IPC_STREAM_CAPTURE) < 0) { fprintf(stderr, "error: load filewrite\n"); ret = -EINVAL; goto exit; } break; case SND_SOC_TPLG_DAPM_BUFFER: - if (tb_register_buffer(tb, ctx) < 0) { + if (tb_new_buffer(tb) < 0) { fprintf(stderr, "error: load buffer\n"); ret = -EINVAL; goto exit; @@ -556,7 +323,7 @@ static int tb_load_widget(struct testbench_prm *tb, struct tplg_context *ctx) break; case SND_SOC_TPLG_DAPM_SCHEDULER: - if (tb_register_pipeline(tb, ctx) < 0) { + if (tb_new_pipeline(tb) < 0) { fprintf(stderr, "error: load pipeline\n"); ret = -EINVAL; goto exit; @@ -564,33 +331,34 @@ static int tb_load_widget(struct testbench_prm *tb, struct tplg_context *ctx) break; case SND_SOC_TPLG_DAPM_SRC: - if (tb_register_src(tb, ctx) < 0) { + if (tb_new_src(tb) < 0) { fprintf(stderr, "error: load src\n"); ret = -EINVAL; goto exit; } break; case SND_SOC_TPLG_DAPM_ASRC: - if (tb_register_asrc(tb, ctx) < 0) { + if (tb_new_asrc(tb) < 0) { fprintf(stderr, "error: load src\n"); ret = -EINVAL; goto exit; } break; case SND_SOC_TPLG_DAPM_MIXER: - if (tb_register_mixer(tb, ctx) < 0) { + if (tb_new_mixer(tb) < 0) { fprintf(stderr, "error: load mixer\n"); ret = -EINVAL; goto exit; } break; case SND_SOC_TPLG_DAPM_EFFECT: - if (tb_register_process(tb, ctx) < 0) { + if (tb_new_process(tb) < 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", @@ -606,16 +374,15 @@ static int tb_load_widget(struct testbench_prm *tb, struct tplg_context *ctx) } /* parse topology file and set up pipeline */ -int tb_parse_topology(struct testbench_prm *tb, struct tplg_context *ctx) +int tb_parse_topology(struct testbench_prm *tb) { + struct tplg_context *ctx = &tb->tplg; struct snd_soc_tplg_hdr *hdr; - struct tplg_comp_info *comp_list_realloc = NULL; - char pipeline_string[256] = {0}; + struct list_item *item; int i; int ret = 0; FILE *file; - size_t size; /* open topology file */ file = fopen(ctx->tplg_file, "rb"); @@ -650,21 +417,26 @@ int tb_parse_topology(struct testbench_prm *tb, struct tplg_context *ctx) } ret = fread(ctx->tplg_base, ctx->tplg_size, 1, file); if (ret != 1) { - fprintf(stderr, "error: can't read topology: %s\n", - strerror(errno)); + 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(&tb->widget_list); + list_init(&tb->route_list); + list_init(&tb->pcm_list); + list_init(&tb->pipeline_list); + while (ctx->tplg_offset < ctx->tplg_size) { /* read next topology header */ hdr = tplg_get_hdr(ctx); - fprintf(stdout, "type: %x, size: 0x%x count: %d index: %d\n", - hdr->type, hdr->payload_size, hdr->count, hdr->index); + tplg_debug("type: %x, size: 0x%x count: %d index: %d\n", + hdr->type, hdr->payload_size, hdr->count, hdr->index); ctx->hdr = hdr; @@ -673,62 +445,513 @@ int tb_parse_topology(struct testbench_prm *tb, struct tplg_context *ctx) /* load dapm widget */ case SND_SOC_TPLG_TYPE_DAPM_WIDGET: - fprintf(stdout, "number of DAPM widgets %d\n", - hdr->count); + tplg_debug("number of DAPM widgets %d\n", hdr->count); /* update max pipeline_id */ ctx->pipeline_id = hdr->index; - tb->info_elems += hdr->count; - size = sizeof(struct tplg_comp_info) * tb->info_elems; - comp_list_realloc = (struct tplg_comp_info *) - realloc(tb->info, size); - - if (!comp_list_realloc && size) { - fprintf(stderr, "error: mem realloc\n"); - ret = -errno; - goto out; - } - tb->info = comp_list_realloc; - - for (i = (tb->info_elems - hdr->count); i < tb->info_elems; i++) - tb->info[i].name = NULL; - - for (tb->info_index = (tb->info_elems - hdr->count); - tb->info_index < tb->info_elems; - tb->info_index++) { - ret = tb_load_widget(tb, ctx); + for (i = 0; i < hdr->count; i++) { + ret = tb_load_widget(tb); if (ret < 0) { - printf("error: loading widget\n"); - goto out; - } else if (ret > 0) - ctx->comp_id++; + 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(ctx, tb->info, - pipeline_string, - tb->info_elems, - hdr->count, - hdr->index) < 0) { + if (tb_register_graph(tb, hdr->count) < 0) { fprintf(stderr, "error: pipeline graph\n"); ret = -EINVAL; goto out; } break; + case SND_SOC_TPLG_TYPE_PCM: + ret = tb_parse_pcm(tb, 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, &tb->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, &tb->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 */ - free(tb->info); - free(ctx->tplg_base); + + // TODO: Check this + // free(ctx->tplg_base); return ret; } +static int tb_prepare_widget(struct testbench_prm *tb, 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(tb, comp_info); + if (ret < 0) + return ret; + + tb_pipeline_update_resource_usage(tb, 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++; + } + + tplg_debug("widget %s prepared\n", comp_info->name); + return 0; +} + +static int tb_prepare_widgets(struct testbench_prm *tb, 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, &tb->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(tb, pcm_info, current_comp_info, 0); + if (ret < 0) + return ret; + } + + /* set up the sink widget */ + ret = tb_prepare_widget(tb, 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(tb, pcm_info, starting_comp_info, + route_info->sink); + if (ret < 0) + return ret; + } + } + + return 0; +} + +static int tb_prepare_widgets_capture(struct testbench_prm *tb, 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, &tb->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(tb, pcm_info, current_comp_info, 1); + if (ret < 0) + return ret; + } + + /* set up the source widget */ + ret = tb_prepare_widget(tb, 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(tb, pcm_info, starting_comp_info, + route_info->source); + if (ret < 0) + return ret; + } + } + + return 0; +} + + +static int tb_set_up_widget(struct testbench_prm *tb, 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(tb, pipe_info); + if (ret < 0) { + pipe_info->usage_count--; + return ret; + } + } + + /* now set up the widget */ + ret = tb_set_up_widget_ipc(tb, comp_info); + if (ret < 0) + return ret; + + tplg_debug("widget %s set up\n", comp_info->name); + + return 0; +} + +static int tb_set_up_widgets(struct testbench_prm *tb, 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, &tb->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(tb, current_comp_info); + if (ret < 0) + return ret; + } + + /* set up the sink widget */ + ret = tb_set_up_widget(tb, route_info->sink); + if (ret < 0) + return ret; + + /* source and sink widgets are up, so set up route now */ + ret = tb_set_up_route(tb, 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(tb, starting_comp_info, route_info->sink); + if (ret < 0) + return ret; + } + } + + return 0; +} + +static int tb_set_up_widgets_capture(struct testbench_prm *tb, + 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, &tb->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(tb, current_comp_info); + if (ret < 0) + return ret; + } + + /* set up the sink widget */ + ret = tb_set_up_widget(tb, route_info->source); + if (ret < 0) + return ret; + + /* source and sink widgets are up, so set up route now */ + ret = tb_set_up_route(tb, 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(tb, starting_comp_info, route_info->source); + if (ret < 0) + return ret; + } + } + + return 0; +} + +int tb_set_up_pipelines(struct testbench_prm *tb, int dir) +{ + struct tplg_comp_info *host = NULL; + struct tplg_pcm_info *pcm_info; + struct list_item *item; + int ret; + + // TODO tb->pcm_id is not defined? + list_for_item(item, &tb->pcm_list) { + pcm_info = container_of(item, struct tplg_pcm_info, item); + + if (pcm_info->id == tb->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", tb->pcm_id); + return -EINVAL; + } + + if (!tb_is_pipeline_enabled(tb, host->pipeline_id)) + return 0; + + tb->pcm_info = pcm_info; // TODO must be an array + + if (dir) { + ret = tb_prepare_widgets_capture(tb, pcm_info, host, host); + if (ret < 0) + return ret; + + ret = tb_set_up_widgets_capture(tb, host, host); + if (ret < 0) + return ret; + + tplg_debug("Setting up capture pipelines complete\n"); + + return 0; + } + + ret = tb_prepare_widgets(tb, pcm_info, host, host); + if (ret < 0) + return ret; + + ret = tb_set_up_widgets(tb, host, host); + if (ret < 0) + return ret; + + tplg_debug("Setting up playback pipelines complete\n"); + + return 0; +} + +static int tb_free_widgets(struct testbench_prm *tb, 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, &tb->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(tb, 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(tb, starting_comp_info, route_info->sink); + if (ret < 0) + return ret; + } + } + + return 0; +} + +static int tb_free_widgets_capture(struct testbench_prm *tb, + 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, &tb->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(tb, 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(tb, starting_comp_info, route_info->source); + if (ret < 0) + return ret; + } + } + + return 0; +} + +int tb_free_pipelines(struct testbench_prm *tb, 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, &tb->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(tb, host->pipeline_id)) + continue; + + if (dir) { + pipeline_list = &tb->pcm_info->capture_pipeline_list; + ret = tb_free_widgets_capture(tb, host, host); + if (ret < 0) { + fprintf(stderr, "failed to free widgets for capture PCM\n"); + return ret; + } + } else { + pipeline_list = &tb->pcm_info->playback_pipeline_list; + ret = tb_free_widgets(tb, 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(tb, pipe_info); + if (ret < 0) + return ret; + } + } + + tb->instance_ids[SND_SOC_TPLG_DAPM_SCHEDULER] = 0; + return 0; +} + +int tb_free_all_pipelines(struct testbench_prm *tb) +{ + debug_print("freeing playback direction\n"); + tb_free_pipelines(tb, SOF_IPC_STREAM_PLAYBACK); + + debug_print("freeing capture direction\n"); + tb_free_pipelines(tb, SOF_IPC_STREAM_CAPTURE); + return 0; +} + +void tb_free_topology(struct testbench_prm *tb) +{ + 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 = &tb->tplg; + struct sof_ipc4_available_audio_format *available_fmts; + struct list_item *item, *_item; + + list_for_item_safe(item, _item, &tb->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, &tb->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, &tb->route_list) { + route_info = container_of(item, struct tplg_route_info, item); + free(route_info); + } + + list_for_item_safe(item, _item, &tb->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); + tplg_debug("freed all pipelines, widgets, routes and pcms\n"); +} diff --git a/tools/testbench/topology_ipc3.c b/tools/testbench/topology_ipc3.c new file mode 100644 index 000000000000..5f80b696aea8 --- /dev/null +++ b/tools/testbench/topology_ipc3.c @@ -0,0 +1,266 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2018-2024 Intel Corporation. All rights reserved. +// +// Author: Ranjani Sridharan <ranjani.sridharan@linux.intel.com> +// Liam Girdwood <liam.r.girdwood@linux.intel.com> +// Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com> + +/* load buffer DAPM widget */ +static int tb_register_buffer(struct testbench_prm *tp) +{ + struct tplg_context *ctx = &tp->tplg; + struct sof *sof = ctx->sof; + struct sof_ipc_buffer buffer = {{{0}}}; + int ret; + + ret = tplg_new_buffer(ctx, &buffer, sizeof(buffer), + NULL, 0); + if (ret < 0) + return ret; + + /* create buffer component */ + if (ipc_buffer_new(sof->ipc, &buffer) < 0) { + fprintf(stderr, "error: buffer new\n"); + return -EINVAL; + } + + return 0; +} + +/* load fileread component */ +static int tb_new_fileread(struct testbench_prm *tp, struct sof_ipc_comp_file *fileread) +{ + struct tplg_context *ctx = &tp->tplg; + struct snd_soc_tplg_vendor_array *array = &ctx->widget->priv.array[0]; + size_t total_array_size = 0; + int size = ctx->widget->priv.size; + int comp_id = ctx->comp_id; + char uuid[UUID_SIZE]; + int ret; + + /* read vendor tokens */ + while (total_array_size < size) { + if (!tplg_is_valid_priv_size(total_array_size, size, array)) { + fprintf(stderr, "error: filewrite array size mismatch for widget size %d\n", + size); + return -EINVAL; + } + + /* parse comp tokens */ + ret = sof_parse_tokens(&fileread->config, comp_tokens, + ARRAY_SIZE(comp_tokens), array, + array->size); + if (ret != 0) { + fprintf(stderr, "error: parse comp tokens %d\n", + size); + return -EINVAL; + } + + /* parse uuid token */ + ret = sof_parse_tokens(uuid, comp_ext_tokens, + ARRAY_SIZE(comp_ext_tokens), array, + array->size); + if (ret != 0) { + fprintf(stderr, "error: parse mixer uuid token %d\n", size); + return -EINVAL; + } + + total_array_size += array->size; + array = MOVE_POINTER_BY_BYTES(array, array->size); + } + + /* configure fileread */ + fileread->mode = FILE_READ; + fileread->comp.id = comp_id; + + /* use fileread comp as scheduling comp */ + fileread->size = sizeof(struct ipc_comp_file); + fileread->comp.core = ctx->core_id; + fileread->comp.hdr.size = sizeof(struct sof_ipc_comp_file) + UUID_SIZE; + fileread->comp.type = SOF_COMP_FILEREAD; + fileread->comp.pipeline_id = ctx->pipeline_id; + fileread->config.hdr.size = sizeof(struct sof_ipc_comp_config); + fileread->comp.ext_data_length = UUID_SIZE; + return 0; +} + +/* load filewrite component */ +static int tb_new_filewrite(struct testbench_prm *tp, struct sof_ipc_comp_file *filewrite) +{ + struct tplg_context *ctx = &tp->tplg; + struct snd_soc_tplg_vendor_array *array = &ctx->widget->priv.array[0]; + size_t total_array_size = 0; + int size = ctx->widget->priv.size; + int comp_id = ctx->comp_id; + char uuid[UUID_SIZE]; + int ret; + + /* read vendor tokens */ + while (total_array_size < size) { + if (!tplg_is_valid_priv_size(total_array_size, size, array)) { + fprintf(stderr, "error: filewrite array size mismatch\n"); + return -EINVAL; + } + + ret = sof_parse_tokens(&filewrite->config, comp_tokens, + ARRAY_SIZE(comp_tokens), array, + array->size); + if (ret != 0) { + fprintf(stderr, "error: parse filewrite tokens %d\n", + size); + return -EINVAL; + } + + /* parse uuid token */ + ret = sof_parse_tokens(uuid, comp_ext_tokens, + ARRAY_SIZE(comp_ext_tokens), array, + array->size); + if (ret != 0) { + fprintf(stderr, "error: parse mixer uuid token %d\n", size); + return -EINVAL; + } + + total_array_size += array->size; + array = MOVE_POINTER_BY_BYTES(array, array->size); + } + + /* configure filewrite */ + filewrite->comp.core = ctx->core_id; + filewrite->comp.id = comp_id; + filewrite->mode = FILE_WRITE; + filewrite->size = sizeof(struct ipc_comp_file); + filewrite->comp.hdr.size = sizeof(struct sof_ipc_comp_file) + UUID_SIZE; + filewrite->comp.type = SOF_COMP_FILEWRITE; + filewrite->comp.pipeline_id = ctx->pipeline_id; + filewrite->config.hdr.size = sizeof(struct sof_ipc_comp_config); + filewrite->comp.ext_data_length = UUID_SIZE; + return 0; +} + +/* load fileread component */ +static int tb_register_fileread(struct testbench_prm *tp, int dir) +{ + struct tplg_context *ctx = &tp->tplg; + struct sof *sof = ctx->sof; + struct sof_ipc_comp_file *fileread; + struct sof_uuid *file_uuid; + int ret; + + fileread = calloc(MAX_TPLG_OBJECT_SIZE, 1); + if (!fileread) + return -ENOMEM; + + fileread->config.frame_fmt = tplg_find_format(tp->bits_in); + + ret = tb_new_fileread(tp, &fileread); + if (ret < 0) + return ret; + + /* configure fileread */ + if (!tp->input_file[tp->input_file_index]) { + fprintf(stderr, + "error: input file [%d] is not defined, add filename to -i f1,f2,...\n", + tp->input_file_index); + return -EINVAL; + } + + fileread.fn = strdup(tp->input_file[tp->input_file_index]); + if (tp->input_file_index == 0) + tp->fr_id = ctx->comp_id; + + /* use fileread comp as scheduling comp */ + ctx->sched_id = ctx->comp_id; + tp->input_file_index++; + + /* Set format from testbench command line*/ + fileread->rate = tp->fs_in; + fileread->channels = tp->channels_in; + fileread->frame_fmt = tp->frame_fmt; + fileread->direction = dir; + + file_uuid = (struct sof_uuid *)((uint8_t *)fileread + sizeof(struct sof_ipc_comp_file)); + file_uuid->a = 0xbfc7488c; + file_uuid->b = 0x75aa; + file_uuid->c = 0x4ce8; + file_uuid->d[0] = 0x9d; + file_uuid->d[1] = 0xbe; + file_uuid->d[2] = 0xd8; + file_uuid->d[3] = 0xda; + file_uuid->d[4] = 0x08; + file_uuid->d[5] = 0xa6; + file_uuid->d[6] = 0x98; + file_uuid->d[7] = 0xc2; + + /* create fileread component */ + if (ipc_comp_new(sof->ipc, ipc_to_comp_new(fileread)) < 0) { + fprintf(stderr, "error: file read\n"); + free(fileread->fn); + return -EINVAL; + } + + free(fileread->fn); + free(fileread); + return 0; +} + +/* load filewrite component */ +static int tb_register_filewrite(struct testbench_prm *tp, int dir) +{ + struct tplg_context *ctx = &tp->tplg; + struct sof *sof = ctx->sof; + struct sof_ipc_comp_file *filewrite; + struct sof_uuid *file_uuid; + int ret; + + filewrite = calloc(MAX_TPLG_OBJECT_SIZE, 1); + if (!filewrite) + return -ENOMEM; + + ret = tb_new_filewrite(tp, filewrite); + if (ret < 0) + return ret; + + /* configure filewrite (multiple output files are supported.) */ + if (!tp->output_file[tp->output_file_index]) { + fprintf(stderr, + "error: output file [%d] is not defined, add filename to -o f1,f2,..\n", + tp->output_file_index); + return -EINVAL; + } + filewrite->fn = strdup(tp->output_file[tp->output_file_index]); + if (tp->output_file_index == 0) + tp->fw_id = ctx->comp_id; + tp->output_file_index++; + + /* Set format from testbench command line*/ + filewrite->rate = tp->fs_out; + filewrite->channels = tp->channels_out; + filewrite->frame_fmt = tp->frame_fmt; + filewrite->direction = dir; + + file_uuid = (struct sof_uuid *)((uint8_t *)filewrite + sizeof(struct sof_ipc_comp_file)); + file_uuid->a = 0xbfc7488c; + file_uuid->b = 0x75aa; + file_uuid->c = 0x4ce8; + file_uuid->d[0] = 0x9d; + file_uuid->d[1] = 0xbe; + file_uuid->d[2] = 0xd8; + file_uuid->d[3] = 0xda; + file_uuid->d[4] = 0x08; + file_uuid->d[5] = 0xa6; + file_uuid->d[6] = 0x98; + file_uuid->d[7] = 0xc2; + + /* create filewrite component */ + if (ipc_comp_new(sof->ipc, ipc_to_comp_new(filewrite)) < 0) { + fprintf(stderr, "error: new file write\n"); + free(filewrite->fn); + return -EINVAL; + } + + free(filewrite->fn); + free(filewrite); + return 0; +} + diff --git a/tools/testbench/topology_ipc4.c b/tools/testbench/topology_ipc4.c new file mode 100644 index 000000000000..47680284b610 --- /dev/null +++ b/tools/testbench/topology_ipc4.c @@ -0,0 +1,812 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2018-2024 Intel Corporation. All rights reserved. +// +// Author: Ranjani Sridharan <ranjani.sridharan@linux.intel.com> +// Liam Girdwood <liam.r.girdwood@linux.intel.com> +// Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com> + +#include <ipc4/header.h> +#include <tplg_parser/tokens.h> +#include <tplg_parser/topology.h> +#include <stdio.h> +#include <stdlib.h> + +#include "testbench/common_test.h" +#include "testbench/topology.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[IPC4_MAX_MSG_SIZE]; + struct ipc4_message_reply *reply_msg = reply; + + + if (len > IPC4_MAX_MSG_SIZE || rlen > IPC4_MAX_MSG_SIZE) { + fprintf(stderr, "ipc: message too big len=%ld rlen=%ld\n", len, rlen); + return -EINVAL; + } + +#if TB_FAKE_IPC + reply_msg->primary.r.status = IPC4_SUCCESS; +#else + + memset(mailbox, 0, IPC4_MAX_MSG_SIZE); + memcpy(mailbox, msg, len); + tb_ipc_message(mailbox, len); + + memcpy(reply, mailbox, rlen); +#endif + + if (reply_msg->primary.r.status != IPC4_SUCCESS) + return -EINVAL; + + return 0; +} + +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); +} + +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 *tb, 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(&tb->ipc_tx, &tb->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 *tb, 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; + 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(&tb->ipc_tx, &tb->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 *tb, 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 = tb->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(&tb->ipc_tx, &tb->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 *tb, + 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 *tb, 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: + break; + } + + 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 = tb->period_size * 2; + base_cfg->obs = tb->period_size * 2; + + return 0; +} + +int tb_set_up_widget_base_config(struct testbench_prm *tb, struct tplg_comp_info *comp_info) +{ + char *config_name = "48k2c32b"; // TODO + int num_configs = 1; // TODO + struct tb_config *config; + bool config_found = false; + int ret, i; + + for (i = 0; i < num_configs; i++) { + config = &tb->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(tb, 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 *tb, 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 *tb, 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 = &tb->pcm_info->capture_pipeline_list; + else + pipeline_list = &tb->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(tb, state, &pipe_state, pipe_info, + &tb->ipc_tx, &tb->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(tb, state, &pipe_state, pipe_info, + &tb->ipc_tx, &tb->ipc_rx); + if (ret < 0) + return ret; + } + + return 0; +} + +/* + * Topology widgets + */ + +int tb_new_aif_in_out(struct testbench_prm *tb, int dir) +{ + struct tplg_context *ctx = &tb->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 = tb->fs_in; + file->config.channels = tb->channels_in; + file->config.frame_fmt = tb->frame_fmt; + file->config.direction = dir; + if (tb->input_file_index >= tb->input_file_num) { + fprintf(stderr, "error: not enough input files\n"); + return -EINVAL; + } + + file->config.fn = tb->input_file[tb->input_file_index]; + comp_info->instance_id = tb->instance_ids[SND_SOC_TPLG_DAPM_AIF_IN]++; + comp_info->module_id = 0x9a; + tb->fr[tb->input_file_index].id = comp_info->module_id; + tb->fr[tb->input_file_index].instance_id = comp_info->instance_id; + tb->fr[tb->input_file_index].pipeline_id = ctx->pipeline_id; + tb->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 = tb->fs_out; + file->config.channels = tb->channels_out; + file->config.frame_fmt = tb->frame_fmt; + file->config.direction = dir; + if (tb->output_file_index >= tb->output_file_num) { + fprintf(stderr, "error: not enough output files\n"); + return -EINVAL; + } + + file->config.fn = tb->output_file[tb->output_file_index]; + comp_info->instance_id = tb->instance_ids[SND_SOC_TPLG_DAPM_AIF_OUT]++; + comp_info->module_id = 0x9b; + tb->fw[tb->output_file_index].id = comp_info->module_id; + tb->fw[tb->output_file_index].instance_id = comp_info->instance_id; + tb->fw[tb->output_file_index].pipeline_id = ctx->pipeline_id; + tb->output_file_index++; + tb_setup_widget_ipc_msg(comp_info); + } + + return 0; +} + +int tb_new_dai_in_out(struct testbench_prm *tb, int dir) +{ + struct tplg_context *ctx = &tb->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 = tb->fs_out; + file->config.channels = tb->channels_out; + file->config.frame_fmt = tb->frame_fmt; + file->config.direction = dir; + if (tb->output_file_index >= tb->output_file_num) { + fprintf(stderr, "error: not enough output files\n"); + return -EINVAL; + } + + file->config.fn = tb->output_file[tb->output_file_index]; + comp_info->instance_id = tb->instance_ids[SND_SOC_TPLG_DAPM_DAI_OUT]++; + comp_info->module_id = 0x9c; + tb->fw[tb->output_file_index].id = comp_info->module_id; + tb->fw[tb->output_file_index].instance_id = comp_info->instance_id; + tb->fw[tb->output_file_index].pipeline_id = ctx->pipeline_id; + tb->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 = tb->fs_in; + file->config.channels = tb->channels_in; + file->config.frame_fmt = tb->frame_fmt; + file->config.direction = dir; + if (tb->input_file_index >= tb->input_file_num) { + fprintf(stderr, "error: not enough input files\n"); + return -EINVAL; + } + + file->config.fn = tb->input_file[tb->input_file_index]; + comp_info->instance_id = tb->instance_ids[SND_SOC_TPLG_DAPM_DAI_IN]++; + comp_info->module_id = 0x9d; + tb->fr[tb->input_file_index].id = comp_info->module_id; + tb->fr[tb->input_file_index].instance_id = comp_info->instance_id; + tb->fr[tb->input_file_index].pipeline_id = ctx->pipeline_id; + tb->input_file_index++; + tb_setup_widget_ipc_msg(comp_info); + } + + return 0; +} + +int tb_new_pga(struct testbench_prm *tb) +{ + struct tplg_context *ctx = &tb->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 = tb->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 *tb) +{ + struct tplg_context *ctx = &tb->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 = tb->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); + return 0; + +out: + free(tplg_ctl); + free(comp_info->ipc_payload); + return ret; + +} + +/* + * To run + */ + +int tb_set_running_state(struct testbench_prm *tb) +{ + int ret; + + ret = tb_pipelines_set_state(tb, 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(tb, 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(tb, 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(tb, 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 *tb) +{ + int ret; + + ret = tb_pipelines_set_state(tb, 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(tb); + + ret = tb_pipelines_set_state(tb, 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(tb); + + ret = tb_pipelines_set_state(tb, 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(tb); + + ret = tb_pipelines_set_state(tb, 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(tb); + return ret; +} + +int tb_delete_pipeline(struct testbench_prm *tb, struct tplg_pipeline_info *pipe_info) +{ + struct ipc4_pipeline_delete msg; + 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(&tb->ipc_tx, &tb->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 *tb, 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; + 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(&tb->ipc_tx, &tb->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; +} diff --git a/tools/tplg_parser/CMakeLists.txt b/tools/tplg_parser/CMakeLists.txt index 1569284b097d..3500f661245c 100644 --- a/tools/tplg_parser/CMakeLists.txt +++ b/tools/tplg_parser/CMakeLists.txt @@ -64,7 +64,7 @@ endif() # TODO: add IPC4 option when it's ready. target_compile_options(sof_tplg_parser PRIVATE - -g -O -Wall -Werror -fPIC -DPIC + -g -Wall -Werror -fPIC -DPIC -Wmissing-prototypes ${implicit_fallthrough} -DCONFIG_LIBRARY -D${tplg_ipc}) 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