sof/tools/testbench/utils_ipc4.c

596 lines
15 KiB
C

// SPDX-License-Identifier: BSD-3-Clause
//
// Copyright(c) 2018-2024 Intel Corporation.
#if CONFIG_IPC_MAJOR_4
#include <sof/audio/component_ext.h>
#include <sof/lib/notifier.h>
#include <sof/audio/component_ext.h>
#include <sof/schedule/edf_schedule.h>
#include <sof/schedule/ll_schedule.h>
#include <sof/schedule/ll_schedule_domain.h>
#include <ipc/stream.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include "testbench/utils.h"
#include "testbench/file.h"
#include "testbench/topology_ipc4.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)
{
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 */