// SPDX-License-Identifier: BSD-3-Clause // // Copyright(c) 2018-2024 Intel Corporation. #if CONFIG_IPC_MAJOR_4 #include #include #include #include #include #include #include #include #include #include #include #include "testbench/utils.h" #include "testbench/file.h" #include "testbench/topology_ipc4.h" #if defined __XCC__ #include #endif SOF_DEFINE_REG_UUID(testbench); DECLARE_TR_CTX(testbench_tr, SOF_UUID(testbench_uuid), LOG_LEVEL_INFO); LOG_MODULE_REGISTER(testbench, CONFIG_SOF_LOG_LEVEL); /* testbench helper functions for pipeline setup and trigger */ int tb_setup(struct sof *sof, struct testbench_prm *tp) { struct ll_schedule_domain domain = {0}; int bits; int krate; domain.next_tick = tp->tick_period_us; /* init components */ sys_comp_init(sof); /* Module adapter components */ sys_comp_module_crossover_interface_init(); sys_comp_module_dcblock_interface_init(); sys_comp_module_demux_interface_init(); sys_comp_module_drc_interface_init(); sys_comp_module_eq_fir_interface_init(); sys_comp_module_eq_iir_interface_init(); sys_comp_module_file_interface_init(); sys_comp_module_gain_interface_init(); sys_comp_module_google_rtc_audio_processing_interface_init(); sys_comp_module_igo_nr_interface_init(); sys_comp_module_mfcc_interface_init(); sys_comp_module_multiband_drc_interface_init(); sys_comp_module_mux_interface_init(); sys_comp_module_rtnr_interface_init(); sys_comp_module_selector_interface_init(); sys_comp_module_src_interface_init(); sys_comp_module_asrc_interface_init(); sys_comp_module_tdfb_interface_init(); sys_comp_module_volume_interface_init(); /* other necessary initializations */ pipeline_posn_init(sof); init_system_notify(sof); /* init IPC */ if (ipc_init(sof) < 0) { fprintf(stderr, "error: IPC init\n"); return -EINVAL; } /* Trace */ ipc_tr.level = LOG_LEVEL_INFO; ipc_tr.uuid_p = SOF_UUID(testbench_uuid); /* init LL scheduler */ if (scheduler_init_ll(&domain) < 0) { fprintf(stderr, "error: edf scheduler init\n"); return -EINVAL; } /* init EDF scheduler */ if (scheduler_init_edf() < 0) { fprintf(stderr, "error: edf scheduler init\n"); return -EINVAL; } tb_debug_print("ipc and scheduler initialized\n"); /* setup IPC4 audio format */ tp->num_configs = 1; krate = tp->fs_in / 1000; switch (tp->frame_fmt) { case SOF_IPC_FRAME_S16_LE: bits = 16; break; case SOF_IPC_FRAME_S24_4LE: bits = 24; break; case SOF_IPC_FRAME_S32_LE: bits = 32; break; default: fprintf(stderr, "error: unsupported frame format %d\n", tp->frame_fmt); return -EINVAL; } /* TODO 44.1 kHz like rates */ snprintf(tp->config[0].name, TB_MAX_CONFIG_NAME_SIZE, "%dk%dc%db", krate, tp->channels_in, bits); tp->num_configs = 1; tp->config[0].buffer_frames = 2 * krate; tp->config[0].buffer_time = 0; tp->config[0].period_frames = krate; tp->config[0].period_time = 0; tp->config[0].rate = tp->fs_in; tp->config[0].channels = tp->channels_in; tp->config[0].format = tp->frame_fmt; tp->period_size = 2 * krate; /* TODO: Need to set this later for larger topologies with multiple PCMs. The * pipelines are determined based on just the PCM ID for the device that we * want to start playback/capture on. */ tp->pcm_id = 0; return 0; } static int tb_prepare_widget(struct testbench_prm *tp, struct tplg_pcm_info *pcm_info, struct tplg_comp_info *comp_info, int dir) { struct tplg_pipeline_list *pipeline_list; int ret, i; if (dir) pipeline_list = &pcm_info->capture_pipeline_list; else pipeline_list = &pcm_info->playback_pipeline_list; /* populate base config */ ret = tb_set_up_widget_base_config(tp, comp_info); if (ret < 0) return ret; tb_pipeline_update_resource_usage(tp, comp_info); /* add pipeline to pcm pipeline_list if needed */ for (i = 0; i < pipeline_list->count; i++) { struct tplg_pipeline_info *pipe_info = pipeline_list->pipelines[i]; if (pipe_info == comp_info->pipe_info) break; } if (i == pipeline_list->count) { pipeline_list->pipelines[pipeline_list->count] = comp_info->pipe_info; pipeline_list->count++; if (pipeline_list->count == TPLG_MAX_PCM_PIPELINES) { fprintf(stderr, "error: pipelines count exceeds %d", TPLG_MAX_PCM_PIPELINES); return -EINVAL; } } return 0; } static int tb_prepare_widgets_playback(struct testbench_prm *tp, struct tplg_pcm_info *pcm_info, struct tplg_comp_info *starting_comp_info, struct tplg_comp_info *current_comp_info) { struct list_item *item; int ret; /* for playback */ list_for_item(item, &tp->route_list) { struct tplg_route_info *route_info = container_of(item, struct tplg_route_info, item); if (route_info->source != current_comp_info) continue; /* set up source widget if it is the starting widget */ if (starting_comp_info == current_comp_info) { ret = tb_prepare_widget(tp, pcm_info, current_comp_info, 0); if (ret < 0) return ret; } /* set up the sink widget */ ret = tb_prepare_widget(tp, pcm_info, route_info->sink, 0); if (ret < 0) return ret; /* and then continue down the path */ if (route_info->sink->type != SND_SOC_TPLG_DAPM_DAI_IN) { ret = tb_prepare_widgets_playback(tp, pcm_info, starting_comp_info, route_info->sink); if (ret < 0) return ret; } } return 0; } static int tb_prepare_widgets_capture(struct testbench_prm *tp, struct tplg_pcm_info *pcm_info, struct tplg_comp_info *starting_comp_info, struct tplg_comp_info *current_comp_info) { struct list_item *item; int ret; /* for capture */ list_for_item(item, &tp->route_list) { struct tplg_route_info *route_info = container_of(item, struct tplg_route_info, item); if (route_info->sink != current_comp_info) continue; /* set up sink widget if it is the starting widget */ if (starting_comp_info == current_comp_info) { ret = tb_prepare_widget(tp, pcm_info, current_comp_info, 1); if (ret < 0) return ret; } /* set up the source widget */ ret = tb_prepare_widget(tp, pcm_info, route_info->source, 1); if (ret < 0) return ret; /* and then continue up the path */ if (route_info->source->type != SND_SOC_TPLG_DAPM_DAI_OUT) { ret = tb_prepare_widgets_capture(tp, pcm_info, starting_comp_info, route_info->source); if (ret < 0) return ret; } } return 0; } static int tb_set_up_widget(struct testbench_prm *tp, struct tplg_comp_info *comp_info) { struct tplg_pipeline_info *pipe_info = comp_info->pipe_info; int ret; pipe_info->usage_count++; /* first set up pipeline if needed, only done once for the first pipeline widget */ if (pipe_info->usage_count == 1) { ret = tb_set_up_pipeline(tp, pipe_info); if (ret < 0) { pipe_info->usage_count--; return ret; } } /* now set up the widget */ return tb_set_up_widget_ipc(tp, comp_info); } static int tb_set_up_widgets_playback(struct testbench_prm *tp, struct tplg_comp_info *starting_comp_info, struct tplg_comp_info *current_comp_info) { struct list_item *item; int ret; /* for playback */ list_for_item(item, &tp->route_list) { struct tplg_route_info *route_info = container_of(item, struct tplg_route_info, item); if (route_info->source != current_comp_info) continue; /* set up source widget if it is the starting widget */ if (starting_comp_info == current_comp_info) { ret = tb_set_up_widget(tp, current_comp_info); if (ret < 0) return ret; } /* set up the sink widget */ ret = tb_set_up_widget(tp, route_info->sink); if (ret < 0) return ret; /* source and sink widgets are up, so set up route now */ ret = tb_set_up_route(tp, route_info); if (ret < 0) return ret; /* and then continue down the path */ if (route_info->sink->type != SND_SOC_TPLG_DAPM_DAI_IN) { ret = tb_set_up_widgets_playback(tp, starting_comp_info, route_info->sink); if (ret < 0) return ret; } } return 0; } static int tb_set_up_widgets_capture(struct testbench_prm *tp, struct tplg_comp_info *starting_comp_info, struct tplg_comp_info *current_comp_info) { struct list_item *item; int ret; /* for playback */ list_for_item(item, &tp->route_list) { struct tplg_route_info *route_info = container_of(item, struct tplg_route_info, item); if (route_info->sink != current_comp_info) continue; /* set up source widget if it is the starting widget */ if (starting_comp_info == current_comp_info) { ret = tb_set_up_widget(tp, current_comp_info); if (ret < 0) return ret; } /* set up the sink widget */ ret = tb_set_up_widget(tp, route_info->source); if (ret < 0) return ret; /* source and sink widgets are up, so set up route now */ ret = tb_set_up_route(tp, route_info); if (ret < 0) return ret; /* and then continue down the path */ if (route_info->source->type != SND_SOC_TPLG_DAPM_DAI_OUT) { ret = tb_set_up_widgets_capture(tp, starting_comp_info, route_info->source); if (ret < 0) return ret; } } return 0; } static int tb_set_up_pipelines(struct testbench_prm *tp, int dir) { struct tplg_comp_info *host = NULL; struct tplg_pcm_info *pcm_info; struct list_item *item; int ret; list_for_item(item, &tp->pcm_list) { pcm_info = container_of(item, struct tplg_pcm_info, item); if (pcm_info->id == tp->pcm_id) { if (dir) host = pcm_info->capture_host; else host = pcm_info->playback_host; break; } } if (!host) { fprintf(stderr, "No host component found for PCM ID: %d\n", tp->pcm_id); return -EINVAL; } if (!tb_is_pipeline_enabled(tp, host->pipeline_id)) return 0; tp->pcm_info = pcm_info; /* TODO must be an array for multiple PCMs */ if (dir) { ret = tb_prepare_widgets_capture(tp, pcm_info, host, host); if (ret < 0) return ret; ret = tb_set_up_widgets_capture(tp, host, host); if (ret < 0) return ret; tb_debug_print("Setting up capture pipelines complete\n"); return 0; } ret = tb_prepare_widgets_playback(tp, pcm_info, host, host); if (ret < 0) return ret; ret = tb_set_up_widgets_playback(tp, host, host); if (ret < 0) return ret; tb_debug_print("Setting up playback pipelines complete\n"); return 0; } int tb_set_up_all_pipelines(struct testbench_prm *tp) { int ret; ret = tb_set_up_pipelines(tp, SOF_IPC_STREAM_PLAYBACK); if (ret) { fprintf(stderr, "error: Failed tb_set_up_pipelines for playback\n"); return ret; } ret = tb_set_up_pipelines(tp, SOF_IPC_STREAM_CAPTURE); if (ret) { fprintf(stderr, "error: Failed tb_set_up_pipelines for capture\n"); return ret; } fprintf(stdout, "pipelines set up complete\n"); return 0; } static int tb_free_widgets_playback(struct testbench_prm *tp, struct tplg_comp_info *starting_comp_info, struct tplg_comp_info *current_comp_info) { struct tplg_route_info *route_info; struct list_item *item; int ret; /* for playback */ list_for_item(item, &tp->route_list) { route_info = container_of(item, struct tplg_route_info, item); if (route_info->source != current_comp_info) continue; /* Widgets will be freed when the pipeline is deleted, so just unbind modules */ ret = tb_free_route(tp, route_info); if (ret < 0) return ret; /* and then continue down the path */ if (route_info->sink->type != SND_SOC_TPLG_DAPM_DAI_IN) { ret = tb_free_widgets_playback(tp, starting_comp_info, route_info->sink); if (ret < 0) return ret; } } return 0; } static int tb_free_widgets_capture(struct testbench_prm *tp, struct tplg_comp_info *starting_comp_info, struct tplg_comp_info *current_comp_info) { struct tplg_route_info *route_info; struct list_item *item; int ret; /* for playback */ list_for_item(item, &tp->route_list) { route_info = container_of(item, struct tplg_route_info, item); if (route_info->sink != current_comp_info) continue; /* Widgets will be freed when the pipeline is deleted, so just unbind modules */ ret = tb_free_route(tp, route_info); if (ret < 0) return ret; /* and then continue down the path */ if (route_info->sink->type != SND_SOC_TPLG_DAPM_DAI_OUT) { ret = tb_free_widgets_capture(tp, starting_comp_info, route_info->source); if (ret < 0) return ret; } } return 0; } static int tb_free_pipelines(struct testbench_prm *tp, int dir) { struct tplg_pipeline_list *pipeline_list; struct tplg_pcm_info *pcm_info; struct list_item *item; struct tplg_comp_info *host = NULL; int ret, i; list_for_item(item, &tp->pcm_list) { pcm_info = container_of(item, struct tplg_pcm_info, item); if (dir) host = pcm_info->capture_host; else host = pcm_info->playback_host; if (!host || !tb_is_pipeline_enabled(tp, host->pipeline_id)) continue; if (dir) { pipeline_list = &tp->pcm_info->capture_pipeline_list; ret = tb_free_widgets_capture(tp, host, host); if (ret < 0) { fprintf(stderr, "failed to free widgets for capture PCM\n"); return ret; } } else { pipeline_list = &tp->pcm_info->playback_pipeline_list; ret = tb_free_widgets_playback(tp, host, host); if (ret < 0) { fprintf(stderr, "failed to free widgets for playback PCM\n"); return ret; } } for (i = 0; i < pipeline_list->count; i++) { struct tplg_pipeline_info *pipe_info = pipeline_list->pipelines[i]; ret = tb_delete_pipeline(tp, pipe_info); if (ret < 0) return ret; } } tp->instance_ids[SND_SOC_TPLG_DAPM_SCHEDULER] = 0; return 0; } int tb_free_all_pipelines(struct testbench_prm *tp) { tb_debug_print("freeing playback direction\n"); tb_free_pipelines(tp, SOF_IPC_STREAM_PLAYBACK); tb_debug_print("freeing capture direction\n"); tb_free_pipelines(tp, SOF_IPC_STREAM_CAPTURE); return 0; } void tb_free_topology(struct testbench_prm *tp) { struct tplg_pcm_info *pcm_info; struct tplg_comp_info *comp_info; struct tplg_route_info *route_info; struct tplg_pipeline_info *pipe_info; struct tplg_context *ctx = &tp->tplg; struct sof_ipc4_available_audio_format *available_fmts; struct list_item *item, *_item; list_for_item_safe(item, _item, &tp->pcm_list) { pcm_info = container_of(item, struct tplg_pcm_info, item); list_item_del(item); free(pcm_info->name); free(pcm_info); } list_for_item_safe(item, _item, &tp->widget_list) { comp_info = container_of(item, struct tplg_comp_info, item); available_fmts = &comp_info->available_fmt; list_item_del(item); free(available_fmts->output_pin_fmts); free(available_fmts->input_pin_fmts); free(comp_info->name); free(comp_info->stream_name); free(comp_info->ipc_payload); free(comp_info); } list_for_item_safe(item, _item, &tp->route_list) { route_info = container_of(item, struct tplg_route_info, item); list_item_del(item); free(route_info); } list_for_item_safe(item, _item, &tp->pipeline_list) { pipe_info = container_of(item, struct tplg_pipeline_info, item); list_item_del(item); free(pipe_info->name); free(pipe_info); } free(ctx->tplg_base); tb_debug_print("freed all pipelines, widgets, routes and pcms\n"); } #endif /* CONFIG_IPC_MAJOR_4 */