From f39bca0e211d4d35736f06d785bc64c423e313cc Mon Sep 17 00:00:00 2001 From: Kamil Paszkiet Date: Tue, 14 Oct 2025 15:25:33 +0200 Subject: [PATCH] TEST: change build-tools.sh to newest version tldr Signed-off-by: Kamil Paszkiet --- scripts/build-tools.sh | 105 +- tools/README.md | 6 +- tools/coredumper/sof-coredump-reader.py | 5 +- tools/corpus/README.md | 21 + tools/corpus/sof-ipc3/DELETE_ME | 1 + tools/corpus/sof-ipc4/DELETE_ME | 1 + tools/ctl/CMakeLists.txt | 4 + tools/ctl/ctl.c | 136 +- tools/ctl/ipc3/eq_fir/flat.txt | 1 + tools/ctl/ipc3/eq_fir/loudness.txt | 1 + tools/ctl/ipc3/eq_fir/mid.txt | 1 + tools/ctl/ipc3/eq_fir/pass.txt | 1 + tools/ctl/ipc3/eq_iir/bandpass.txt | 1 + tools/ctl/ipc3/eq_iir/bassboost.txt | 1 + tools/ctl/ipc3/eq_iir/bundle.txt | 1 + tools/ctl/ipc3/eq_iir/flat.txt | 1 + .../ipc3/eq_iir/highpass_20hz_0db_48khz.txt | 1 + .../ipc3/eq_iir/highpass_30hz_0db_48khz.txt | 1 + .../ipc3/eq_iir/highpass_40hz_0db_48khz.txt | 1 + .../ipc3/eq_iir/highpass_50hz_0db_48khz.txt | 1 + tools/ctl/ipc3/eq_iir/loudness.txt | 1 + tools/ctl/ipc3/eq_iir/pass.txt | 1 + .../ctl/ipc4/drc/generic_notebook_speaker.txt | 1 + tools/ctl/ipc4/drc/passthrough.txt | 1 + tools/ctl/ipc4/eq_fir/flat.txt | 1 + tools/ctl/ipc4/eq_fir/loudness.txt | 1 + tools/ctl/ipc4/eq_fir/mid.txt | 1 + tools/ctl/ipc4/eq_fir/pass.txt | 1 + tools/ctl/ipc4/eq_iir/bandpass.txt | 1 + tools/ctl/ipc4/eq_iir/bassboost.txt | 1 + tools/ctl/ipc4/eq_iir/flat.txt | 1 + .../ipc4/eq_iir/highpass_20hz_0db_48khz.txt | 1 + .../ipc4/eq_iir/highpass_30hz_0db_48khz.txt | 1 + .../ipc4/eq_iir/highpass_40hz_0db_48khz.txt | 1 + .../ipc4/eq_iir/highpass_40hz_20db_48khz.txt | 1 + .../ipc4/eq_iir/highpass_40hz_30db_48khz.txt | 1 + .../ipc4/eq_iir/highpass_40hz_40db_48khz.txt | 1 + .../ipc4/eq_iir/highpass_50hz_0db_48khz.txt | 1 + tools/ctl/ipc4/eq_iir/loudness.txt | 1 + tools/ctl/ipc4/eq_iir/pass.txt | 1 + tools/debug_stream/debug_stream.py | 603 ++ tools/demo-gui/README-dev.md | 40 + tools/demo-gui/README.md | 37 + .../demo-gui/sof_controller_engine.py | 243 + tools/demo-gui/demo-gui/sof_demo_gui.py | 257 + tools/demo-gui/demo-gui/sof_demo_tui.py | 184 + tools/logger/CMakeLists.txt | 18 +- tools/logger/config.h.in | 1 + tools/logger/convert.c | 137 +- tools/logger/convert.h | 8 +- tools/logger/filter.c | 4 +- tools/logger/logger.c | 199 +- tools/logger/misc.c | 2 - tools/mtrace/mtrace-reader.c | 61 + tools/mtrace/mtrace-reader.py | 53 + tools/plugin/CMakeLists.txt | 72 + tools/plugin/README.md | 161 + tools/plugin/alsaconf/50-sof.conf | 94 + tools/plugin/alsaconf/CMakeLists.txt | 5 + tools/plugin/alsaplug/CMakeLists.txt | 69 + tools/plugin/alsaplug/conf.c | 47 + tools/plugin/alsaplug/ctl.c | 845 ++ tools/plugin/alsaplug/pcm.c | 1000 +++ tools/plugin/alsaplug/plugin.c | 361 + tools/plugin/alsaplug/plugin.h | 68 + tools/plugin/alsaplug/tplg.c | 1554 ++++ tools/plugin/alsaplug/tplg_ctl.c | 226 + tools/plugin/common.c | 466 ++ tools/plugin/common.h | 328 + tools/plugin/modules/CMakeLists.txt | 59 + tools/plugin/modules/alsa.c | 788 ++ .../ov_noise_suppression/CMakeLists.txt | 42 + .../ov_noise_suppression/noise_suppression.c | 76 + .../noise_suppression_interface.cpp | 186 + .../noise_suppression_interface.h | 26 + tools/plugin/modules/shm.c | 445 ++ tools/plugin/pipe/CMakeLists.txt | 26 + tools/plugin/pipe/cpu.c | 186 + tools/plugin/pipe/ipc4.c | 426 + tools/plugin/pipe/main.c | 345 + tools/plugin/pipe/pipe.h | 125 + tools/plugin/pipe/pipeline.c | 356 + tools/probes/CMakeLists.txt | 7 +- tools/probes/probes_demux.c | 338 + tools/probes/probes_demux.h | 29 + tools/probes/probes_main.c | 302 +- tools/rimage/.checkpatch.conf | 6 + tools/rimage/CMakeLists.txt | 67 + tools/rimage/LICENSE | 97 + tools/rimage/README.md | 105 + tools/rimage/config/acp_6_0.toml | 15 + tools/rimage/config/acp_6_3.toml | 20 + tools/rimage/config/acp_7_0.toml | 20 + tools/rimage/config/apl.toml | 57 + tools/rimage/config/bdw.toml | 15 + tools/rimage/config/bsw.toml | 15 + tools/rimage/config/byt.toml | 15 + tools/rimage/config/cht.toml | 15 + tools/rimage/config/cnl.toml | 61 + tools/rimage/config/hsw.toml | 15 + tools/rimage/config/icl.toml | 61 + tools/rimage/config/imx8.toml | 20 + tools/rimage/config/imx8m.toml | 20 + tools/rimage/config/imx8ulp.toml | 20 + tools/rimage/config/imx8x.toml | 20 + tools/rimage/config/imx95.toml | 9 + tools/rimage/config/jsl.toml | 61 + tools/rimage/config/kbl.toml | 30 + tools/rimage/config/lnl.toml.h | 154 + tools/rimage/config/mt8186.toml | 15 + tools/rimage/config/mt8188.toml | 15 + tools/rimage/config/mt8195.toml | 15 + tools/rimage/config/mt8196.toml | 15 + tools/rimage/config/mt8365.toml | 15 + tools/rimage/config/mtl.toml.h | 174 + tools/rimage/config/nvl.toml.h | 146 + tools/rimage/config/platform-lnl.toml | 56 + tools/rimage/config/platform-mtl.toml | 56 + tools/rimage/config/platform-nvl.toml | 56 + tools/rimage/config/platform-ptl.toml | 56 + tools/rimage/config/platform.toml | 7 + tools/rimage/config/ptl.toml.h | 154 + tools/rimage/config/rmb.toml | 15 + tools/rimage/config/rn.toml | 15 + tools/rimage/config/skl.toml | 30 + tools/rimage/config/sue.toml | 31 + tools/rimage/config/tgl-h.toml | 641 ++ tools/rimage/config/tgl.toml | 641 ++ tools/rimage/config/vangogh.toml | 15 + tools/rimage/config/wcl.toml.h | 150 + tools/rimage/scripts/checkpatch.pl | 6845 +++++++++++++++++ tools/rimage/scripts/const_structs.checkpatch | 0 tools/rimage/scripts/spelling.txt | 1254 +++ tools/rimage/src/adsp_config.c | 2376 ++++++ tools/rimage/src/cse.c | 138 + tools/rimage/src/css.c | 185 + tools/rimage/src/elf_file.c | 561 ++ tools/rimage/src/ext_manifest.c | 247 + tools/rimage/src/file_simple.c | 295 + tools/rimage/src/file_utils.c | 65 + tools/rimage/src/hash.c | 196 + tools/rimage/src/include/rimage/adsp_config.h | 9 + .../include/rimage/cavs/cavs_ext_manifest.h | 251 + tools/rimage/src/include/rimage/cse.h | 47 + tools/rimage/src/include/rimage/css.h | 108 + tools/rimage/src/include/rimage/elf.h | 936 +++ tools/rimage/src/include/rimage/elf_file.h | 178 + .../src/include/rimage/ext_manifest_gen.h | 37 + tools/rimage/src/include/rimage/file_utils.h | 39 + tools/rimage/src/include/rimage/hash.h | 111 + tools/rimage/src/include/rimage/manifest.h | 239 + tools/rimage/src/include/rimage/misc_utils.h | 32 + tools/rimage/src/include/rimage/module.h | 196 + tools/rimage/src/include/rimage/plat_auth.h | 155 + tools/rimage/src/include/rimage/rimage.h | 176 + .../include/rimage/sof/kernel/ext_manifest.h | 81 + .../rimage/src/include/rimage/sof/kernel/fw.h | 80 + .../src/include/rimage/sof/user/manifest.h | 279 + tools/rimage/src/include/rimage/toml_utils.h | 131 + tools/rimage/src/manifest.c | 1788 +++++ tools/rimage/src/misc_utils.c | 51 + tools/rimage/src/module.c | 561 ++ tools/rimage/src/pkcs1_5.c | 970 +++ tools/rimage/src/plat_auth.c | 93 + tools/rimage/src/rimage.c | 288 + tools/rimage/src/toml_utils.c | 490 ++ tools/sof_ri_info/sof_ri_info.py | 346 +- tools/test/audio/README.md | 107 + tools/test/audio/comp_run.sh | 148 +- tools/test/audio/process_test.m | 686 +- tools/test/audio/src_test.m | 109 +- tools/test/audio/std_utils/aap_test_measure.m | 6 +- tools/test/audio/std_utils/aip_test_measure.m | 6 +- tools/test/audio/std_utils/fr_test_measure.m | 6 +- tools/test/audio/std_utils/ldlg_test_input.m | 72 + .../test/audio/std_utils/ldlg_test_measure.m | 80 + tools/test/audio/std_utils/stdlpf.m | 4 +- tools/test/audio/std_utils/stdlpf_get.m | 2 +- tools/test/audio/std_utils/thdnf_test_input.m | 2 +- .../test/audio/std_utils/thdnf_test_measure.m | 5 +- tools/test/audio/tdfb_direction_test.m | 1 + tools/test/audio/tdfb_enable.sh | 5 + tools/test/audio/tdfb_test.m | 24 +- .../audio/test_utils/chirp_test_analyze.m | 10 + .../audio/test_utils/dither_and_quantize.m | 3 +- tools/test/audio/test_utils/mix_sweep.m | 6 +- tools/test/audio/test_utils/test_run.m | 18 +- tools/test/topology/platform/generic.m4 | 60 + tools/test/topology/test-all.m4 | 8 +- tools/test/topology/test-capture.m4 | 9 +- tools/test/topology/test-playback.m4 | 20 +- tools/test/topology/test-tone-playback.m4 | 4 +- tools/test/topology/tplg-build.sh | 6 +- tools/testbench/CMakeLists.txt | 80 +- tools/testbench/README.md | 325 + tools/testbench/file.c | 573 +- tools/testbench/include/linux/types.h | 22 + tools/testbench/include/testbench/file.h | 51 +- tools/testbench/include/testbench/file_ipc4.h | 26 + .../include/testbench/topology_ipc4.h | 66 + tools/testbench/include/testbench/trace.h | 10 +- tools/testbench/include/testbench/utils.h | 179 + tools/testbench/testbench.c | 901 +-- tools/testbench/testbench_xcc_sections.txt | 18 + tools/testbench/topology_ipc3.c | 761 ++ tools/testbench/topology_ipc4.c | 1891 +++++ tools/testbench/utils.c | 577 ++ tools/testbench/utils_ipc3.c | 443 ++ tools/testbench/utils_ipc4.c | 703 ++ tools/topology/CMakeLists.txt | 70 +- tools/topology/topology1/CMakeLists.txt | 347 +- .../topology1/development/CMakeLists.txt | 96 +- tools/topology/topology1/m4/buffer.m4 | 9 +- .../m4/crossover/coef_2way_48000_200_1_2.m4 | 19 + .../coef_3way_48000_200_1000_1_2_3.m4 | 33 + .../coef_4way_48000_200_1000_3000_1_2_3_4.m4 | 33 + tools/topology/topology1/m4/dai.m4 | 2 +- .../topology1/m4/drc_coef_speaker_default.m4 | 22 + tools/topology/topology1/m4/eap.m4 | 69 + tools/topology/topology1/m4/eap_controls.m4 | 12 + .../m4/google_ctc_audio_processing.m4 | 68 + ...oogle_ctc_audio_processing_coef_default.m4 | 6 + tools/topology/topology1/m4/mfcc.m4 | 65 + .../topology/topology1/m4/mfcc/mfcc_config.m4 | 20 + .../m4/multiband_drc_coef_default.m4 | 68 +- .../m4/multiband_drc_coef_passthrough.m4 | 70 + tools/topology/topology1/m4/pcm.m4 | 15 +- tools/topology/topology1/m4/pipeline.m4 | 27 - tools/topology/topology1/platform/amd/acp.m4 | 23 + .../topology1/platform/common/acp-hs.m4 | 73 + .../topology1/platform/common/acp-sdw.m4 | 24 + .../topology1/platform/common/acp-sp.m4 | 38 +- .../topology1/platform/common/esai.m4 | 2 +- .../topology1/platform/common/micfil.m4 | 24 + .../topology/topology1/platform/common/sai.m4 | 4 +- .../topology/topology1/platform/common/ssp.m4 | 2 +- .../topology1/platform/mediatek/mt8188.m4 | 23 + .../topology1/platform/mediatek/mt8196.m4 | 23 + .../topology1/platform/mediatek/mt8365.m4 | 23 + tools/topology/topology1/sof-acp-renoir.m4 | 6 +- .../topology/topology1/sof-acp-rmb-dmic4ch.m4 | 85 + ...acp-rmb-tdm8ch-dmic2ch-nau8825-max98360.m4 | 197 + ...of-acp-rmb-tdm8ch-dmic4ch-rt5682-rt1019.m4 | 198 + tools/topology/topology1/sof-acp-rmb.m4 | 84 + tools/topology/topology1/sof-acp-vangogh.m4 | 68 + tools/topology/topology1/sof-acp_6_3.m4 | 98 + tools/topology/topology1/sof-acp_6_3_sdw.m4 | 136 + tools/topology/topology1/sof-acp_7_0.m4 | 98 + tools/topology/topology1/sof-acp_7_0_sdw.m4 | 150 + tools/topology/topology1/sof-cavs-nocodec.m4 | 78 +- .../sof-eq-iir-dts-codec-smart-amplifier.m4 | 4 +- .../sof-imx8-compr-pcm-cap-wm8960.m4 | 91 + .../topology1/sof-imx8-compr-pcm-wm8960.m4 | 91 + .../topology1/sof-imx8-compr-wm8960-mixer.m4 | 61 +- .../topology1/sof-imx8-compr-wm8960.m4 | 116 + .../topology1/sof-imx8-cs42888-mixer.m4 | 12 +- tools/topology/topology1/sof-imx8-cs42888.m4 | 8 +- .../topology1/sof-imx8-nocodec-sai.m4 | 6 +- tools/topology/topology1/sof-imx8-nocodec.m4 | 6 +- .../topology1/sof-imx8-src-cs42888.m4 | 82 + .../topology/topology1/sof-imx8-src-wm8960.m4 | 104 + .../topology1/sof-imx8-wm8960-cs42888.m4 | 118 + .../topology/topology1/sof-imx8-wm8960-kwd.m4 | 6 +- .../topology1/sof-imx8-wm8960-mixer.m4 | 66 +- tools/topology/topology1/sof-imx8-wm8960.m4 | 57 +- .../topology1/sof-imx8mp-btsco-dual-8ch.m4 | 124 + .../sof-imx8mp-compr-pcm-cap-wm8960.m4 | 92 + .../topology1/sof-imx8mp-compr-pcm-wm8960.m4 | 92 + .../topology1/sof-imx8mp-compr-wm8960.m4 | 117 + tools/topology/topology1/sof-imx8mp-micfil.m4 | 35 + .../topology1/sof-imx8mp-wm8960-kwd.m4 | 4 +- .../topology1/sof-imx8ulp-9x9-btsco.m4 | 30 +- tools/topology/topology1/sof-imx8ulp-btsco.m4 | 31 +- tools/topology/topology1/sof-imx93-wm8962.m4 | 82 + tools/topology/topology1/sof-imx95-wm8962.m4 | 82 + tools/topology/topology1/sof-mt8186-mt6366.m4 | 24 +- tools/topology/topology1/sof-mt8188-mt6359.m4 | 128 + .../sof-mt8195-mt6359-rt1019-rt5682.m4 | 14 +- tools/topology/topology1/sof-mt8196-mt6681.m4 | 153 + tools/topology/topology1/sof-mt8365-mt6357.m4 | 128 + .../topology1/sof-rn-rt5682-max98360.m4 | 6 +- .../topology1/sof-rn-rt5682-rt1019.m4 | 21 +- .../topology/topology1/sof-smart-amplifier.m4 | 5 +- .../topology1/sof/pipe-asrc-playback.m4 | 2 +- .../sof/pipe-codec-adapter-capture.m4 | 20 +- .../sof/pipe-codec-adapter-playback.m4 | 20 +- .../topology1/sof/pipe-crossover-playback.m4 | 6 +- .../topology1/sof/pipe-ctc-playback.m4 | 94 + .../topology1/sof/pipe-drc-playback.m4 | 2 +- .../sof/pipe-dts-codec-eq-iir-playback.m4 | 108 + .../topology1/sof/pipe-dts-codec-playback.m4 | 76 + .../topology1/sof/pipe-eap-playback.m4 | 70 + .../sof/pipe-eq-iir-codec-adapter-playback.m4 | 20 +- .../sof/pipe-eq-iir-mfcc-capture-16khz.m4 | 119 + .../sof/pipe-gui-components-capture.m4 | 146 + .../sof/pipe-gui-components-playback.m4 | 114 + .../sof/pipe-host-codec-adapter-playback.m4 | 20 +- .../topology1/sof/pipe-host-playback.m4 | 56 + .../sof/pipe-host-src-volume-playback.m4 | 110 + .../sof/pipe-low-latency-capture-demux.m4 | 107 + .../pipe-low-latency-playback_mux_tdm4ch.m4 | 116 + .../topology1/sof/pipe-mfcc-capture.m4 | 88 + .../topology1/sof/pipe-mfcc-playback.m4 | 88 + ...mixer-eq-iir-eq-fir-volume-dai-playback.m4 | 3 + .../pipe-mixer-eq-iir-volume-dai-playback.m4 | 121 + .../topology1/sof/pipe-mux-dai-playback.m4 | 70 + tools/topology/topology1/sof/pipe-pcm-mux.m4 | 87 + ...pipe-virtual-passthrough-playback-sched.m4 | 57 + .../sof/pipe-virtual-playback-passthrough.m4 | 23 + .../topology1/sof/pipe-volume-capture.m4 | 5 +- .../sof/pipe-waves-codec-ctc-playback.m4 | 155 + .../sof/pipe-waves-codec-demux-playback.m4 | 4 +- .../sof/pipe-waves-codec-playback.m4 | 5 +- tools/topology/topology1/sof/tokens.m4 | 17 +- tools/topology/topology2/CMakeLists.txt | 45 +- .../topology2/cavs-benchmark-hda.conf | 917 +++ .../topology2/cavs-benchmark-sdw.conf | 557 ++ tools/topology/topology2/cavs-cs35l56.conf | 423 + tools/topology/topology2/cavs-es83x6.conf | 234 + .../topology2/cavs-mixin-mixout-ctc-ssp.conf | 106 + .../topology2/cavs-mixin-mixout-efx-hda.conf | 419 + .../cavs-mixin-mixout-eqiir-dts-ssp.conf | 168 + .../topology2/cavs-mixin-mixout-hda.conf | 431 ++ .../topology2/cavs-mixin-mixout-ssp.conf | 144 + tools/topology/topology2/cavs-nocodec-bt.conf | 684 ++ tools/topology/topology2/cavs-nocodec.conf | 2177 ++++++ .../topology2/cavs-passthrough-hda.conf | 362 + tools/topology/topology2/cavs-rt5682.conf | 351 + .../topology2/cavs-sdw-src-gain-mixin.conf | 218 + tools/topology/topology2/cavs-sdw.conf | 139 + .../topology2/cavs-src-mixin-mixout-hda.conf | 150 + .../topology2/development/CMakeLists.txt | 28 + .../development/cavs-nocodec-crossover.conf | 382 + .../development/cavs-nocodec-rtcaec.conf | 373 + .../topology2/development/sof-plugin.conf | 347 + .../development/tplg-targets-bench.cmake | 99 + .../topology2/development/tplg-targets.cmake | 381 + tools/topology/topology2/doc/CMakeLists.txt | 47 + tools/topology/topology2/doc/README | 7 + .../doc/extra-contents/mainpage.doxy | 32 + tools/topology/topology2/doc/sof.doxygen.in | 34 + .../topology2/doc/topology2-filter.py | 745 ++ .../doc/topology2-generate-contents.sh | 31 + tools/topology/topology2/get_abi.sh | 8 +- tools/topology/topology2/imx8-wm8960.conf | 127 + .../include/bench/aria_controls_capture.conf | 19 + .../include/bench/aria_controls_playback.conf | 19 + .../topology2/include/bench/aria_route.conf | 19 + .../topology2/include/bench/aria_s24.conf | 13 + .../topology2/include/bench/asrc_route.conf | 19 + .../topology2/include/bench/asrc_s16.conf | 19 + .../topology2/include/bench/asrc_s24.conf | 19 + .../topology2/include/bench/asrc_s32.conf | 19 + .../include/bench/bench_comp_generate.sh | 122 + .../bench/dcblock_control_bytes_capture.conf | 8 + .../bench/dcblock_control_bytes_playback.conf | 8 + .../include/bench/dcblock_route.conf | 19 + .../topology2/include/bench/dcblock_s16.conf | 13 + .../topology2/include/bench/dcblock_s24.conf | 13 + .../topology2/include/bench/dcblock_s32.conf | 13 + .../include/bench/drc_controls_capture.conf | 19 + .../include/bench/drc_controls_playback.conf | 19 + .../topology2/include/bench/drc_route.conf | 19 + .../topology2/include/bench/drc_s16.conf | 13 + .../topology2/include/bench/drc_s24.conf | 13 + .../topology2/include/bench/drc_s32.conf | 13 + .../bench/eqfir_control_bytes_capture.conf | 12 + .../bench/eqfir_control_bytes_playback.conf | 12 + .../topology2/include/bench/eqfir_route.conf | 19 + .../topology2/include/bench/eqfir_s16.conf | 13 + .../topology2/include/bench/eqfir_s24.conf | 13 + .../topology2/include/bench/eqfir_s32.conf | 13 + .../bench/eqiir_control_bytes_capture.conf | 16 + .../bench/eqiir_control_bytes_playback.conf | 16 + .../topology2/include/bench/eqiir_route.conf | 19 + .../topology2/include/bench/eqiir_s16.conf | 13 + .../topology2/include/bench/eqiir_s24.conf | 13 + .../topology2/include/bench/eqiir_s32.conf | 13 + .../include/bench/gain_controls_capture.conf | 16 + .../include/bench/gain_controls_playback.conf | 16 + .../topology2/include/bench/gain_route.conf | 19 + .../topology2/include/bench/gain_s16.conf | 13 + .../topology2/include/bench/gain_s24.conf | 13 + .../topology2/include/bench/gain_s32.conf | 13 + .../host_gateway_pipelines_multich_s32.conf | 438 ++ .../bench/host_gateway_pipelines_s16.conf | 66 + .../bench/host_gateway_pipelines_s24.conf | 66 + .../bench/host_gateway_pipelines_s32.conf | 66 + .../bench/host_gateway_pipelines_src_s16.conf | 24 + .../bench/host_gateway_pipelines_src_s24.conf | 24 + .../bench/host_gateway_pipelines_src_s32.conf | 24 + .../bench/host_io_gateway_pipelines_s16.conf | 126 + .../bench/host_io_gateway_pipelines_s24.conf | 126 + .../bench/host_io_gateway_pipelines_s32.conf | 126 + .../bench/igo_nr_controls_capture.conf | 43 + .../bench/igo_nr_controls_playback.conf | 43 + .../topology2/include/bench/igo_nr_route.conf | 19 + .../topology2/include/bench/igo_nr_s16.conf | 13 + .../topology2/include/bench/igo_nr_s24.conf | 13 + .../topology2/include/bench/igo_nr_s32.conf | 13 + .../level_multiplier_controls_capture.conf | 17 + .../level_multiplier_controls_playback.conf | 17 + .../include/bench/level_multiplier_route.conf | 19 + .../include/bench/level_multiplier_s16.conf | 13 + .../include/bench/level_multiplier_s24.conf | 13 + .../include/bench/level_multiplier_s32.conf | 13 + .../bench/micsel_controls_capture.conf | 18 + .../bench/micsel_controls_playback.conf | 18 + .../include/bench/micsel_hda_route.conf | 19 + .../include/bench/micsel_multich_s32.conf | 138 + .../topology2/include/bench/micsel_route.conf | 19 + .../topology2/include/bench/micsel_s16.conf | 13 + .../topology2/include/bench/micsel_s24.conf | 13 + .../topology2/include/bench/micsel_s32.conf | 13 + .../bench/multiband_drc_controls_capture.conf | 16 + .../multiband_drc_controls_playback.conf | 16 + .../include/bench/multiband_drc_route.conf | 19 + .../include/bench/multiband_drc_s16.conf | 13 + .../include/bench/multiband_drc_s24.conf | 13 + .../include/bench/multiband_drc_s32.conf | 13 + .../bench/one_input_output_format_s16.conf | 17 + .../bench/one_input_output_format_s24.conf | 17 + .../bench/one_input_output_format_s32.conf | 17 + .../include/bench/rtnr_controls_capture.conf | 23 + .../include/bench/rtnr_controls_playback.conf | 23 + .../topology2/include/bench/rtnr_route.conf | 19 + .../topology2/include/bench/rtnr_s16.conf | 13 + .../topology2/include/bench/rtnr_s24.conf | 13 + .../topology2/include/bench/rtnr_s32.conf | 13 + .../bench/sound_dose_controls_capture.conf | 33 + .../bench/sound_dose_controls_playback.conf | 33 + .../include/bench/sound_dose_route.conf | 19 + .../include/bench/sound_dose_s16.conf | 13 + .../include/bench/sound_dose_s24.conf | 13 + .../include/bench/sound_dose_s32.conf | 13 + .../include/bench/src_lite_route.conf | 19 + .../topology2/include/bench/src_lite_s16.conf | 13 + .../topology2/include/bench/src_lite_s24.conf | 13 + .../topology2/include/bench/src_lite_s32.conf | 13 + .../topology2/include/bench/src_route.conf | 19 + .../topology2/include/bench/src_s16.conf | 13 + .../topology2/include/bench/src_s24.conf | 13 + .../topology2/include/bench/src_s32.conf | 13 + .../include/bench/tdfb_controls_capture.conf | 23 + .../include/bench/tdfb_controls_playback.conf | 23 + .../topology2/include/bench/tdfb_route.conf | 19 + .../topology2/include/bench/tdfb_s16.conf | 13 + .../topology2/include/bench/tdfb_s24.conf | 13 + .../topology2/include/bench/tdfb_s32.conf | 13 + .../bench/template_comp_controls_capture.conf | 17 + .../template_comp_controls_playback.conf | 17 + .../include/bench/template_comp_route.conf | 19 + .../include/bench/template_comp_s16.conf | 13 + .../include/bench/template_comp_s24.conf | 13 + .../include/bench/template_comp_s32.conf | 13 + .../include/common/common_definitions.conf | 70 + .../topology2/include/common/data.conf | 22 +- .../topology2/include/common/fe_dai.conf | 32 +- .../include/common/input_audio_format.conf | 142 + .../include/common/input_pin_binding.conf | 34 + .../topology2/include/common/manifest.conf | 6 +- .../include/common/output_audio_format.conf | 139 + .../include/common/output_pin_binding.conf | 37 + .../topology2/include/common/pcm.conf | 22 +- .../topology2/include/common/pcm_caps.conf | 24 +- .../topology2/include/common/tokens.conf | 91 +- .../include/common/vendor-token.conf | 10 +- .../include/components/alh-dai-copier.conf | 133 + .../topology2/include/components/aria.conf | 99 + .../include/components/aria/param_1.conf | 10 + .../include/components/aria/param_2.conf | 10 + .../include/components/aria/param_3.conf | 10 + .../include/components/aria/passthrough.conf | 10 + .../topology2/include/components/asrc.conf | 39 +- .../topology2/include/components/buffer.conf | 12 +- .../include/components/crossover.conf | 63 + .../crossover/coef_2way_48000_200_0_1.conf | 20 + .../coef_3way_48000_200_1000_0_1_2.conf | 34 + ...coef_4way_48000_200_1000_3000_0_1_2_3.conf | 34 + .../topology2/include/components/ctc.conf | 101 + .../include/components/dai-copier.conf | 171 + .../topology2/include/components/dai.conf | 22 +- .../topology2/include/components/dcblock.conf | 65 + .../components/dcblock/100hz_16khz.conf | 13 + .../components/dcblock/100hz_48khz.conf | 13 + .../components/dcblock/200hz_16khz.conf | 13 + .../components/dcblock/200hz_48khz.conf | 13 + .../components/dcblock/20hz_16khz.conf | 13 + .../components/dcblock/20hz_48khz.conf | 13 + .../components/dcblock/30hz_16khz.conf | 13 + .../components/dcblock/30hz_48khz.conf | 13 + .../components/dcblock/40hz_16khz.conf | 13 + .../components/dcblock/40hz_48khz.conf | 13 + .../components/dcblock/50hz_16khz.conf | 13 + .../components/dcblock/50hz_48khz.conf | 13 + .../components/dcblock/80hz_16khz.conf | 13 + .../components/dcblock/80hz_48khz.conf | 13 + .../include/components/dcblock/default.conf | 13 + .../topology2/include/components/drc.conf | 84 + .../include/components/drc/default.conf | 22 + .../include/components/drc/dmic_default.conf | 23 + .../include/components/drc/enabled.conf | 23 + .../include/components/drc/passthrough.conf | 23 + .../components/drc/speaker_default.conf | 23 + .../topology2/include/components/dts.conf | 107 + .../include/components/dts/dts_spk.conf | 9 + .../topology2/include/components/eqfir.conf | 64 + .../include/components/eqfir/flat.conf | 15 + .../include/components/eqfir/loudness.conf | 81 + .../include/components/eqfir/midboost.conf | 24 + .../include/components/eqfir/passthrough.conf | 15 + .../topology2/include/components/eqiir.conf | 28 +- .../include/components/eqiir/bandpass.conf | 23 + .../include/components/eqiir/bassboost.conf | 23 + .../include/components/eqiir/flat.conf | 19 + .../eqiir/highpass_100hz_0db_16khz.conf | 19 + .../eqiir/highpass_100hz_0db_48khz.conf | 19 + .../eqiir/highpass_100hz_20db_16khz.conf | 19 + .../eqiir/highpass_100hz_20db_48khz.conf | 19 + .../eqiir/highpass_20hz_0db_16khz.conf | 19 + .../eqiir/highpass_20hz_0db_48khz.conf | 19 + .../eqiir/highpass_20hz_20db_16khz.conf | 19 + .../eqiir/highpass_20hz_20db_48khz.conf | 19 + .../eqiir/highpass_30hz_0db_16khz.conf | 19 + .../eqiir/highpass_30hz_0db_48khz.conf | 19 + .../eqiir/highpass_30hz_20db_16khz.conf | 19 + .../eqiir/highpass_30hz_20db_48khz.conf | 19 + .../eqiir/highpass_40hz_0db_16khz.conf | 19 + .../eqiir/highpass_40hz_0db_48khz.conf | 19 + .../eqiir/highpass_40hz_20db_16khz.conf | 19 + .../eqiir/highpass_40hz_20db_48khz.conf | 19 + .../eqiir/highpass_50hz_0db_16khz.conf | 19 + .../eqiir/highpass_50hz_0db_48khz.conf | 19 + .../eqiir/highpass_50hz_20db_16khz.conf | 19 + .../eqiir/highpass_50hz_20db_48khz.conf | 19 + .../include/components/eqiir/loudness.conf | 34 + .../include/components/eqiir/passthrough.conf | 19 + .../topology2/include/components/gain.conf | 158 +- .../include/components/google-rtc-aec.conf | 159 + .../include/components/host-copier.conf | 128 + .../topology2/include/components/host.conf | 12 - .../topology2/include/components/igo_nr.conf | 100 + .../topology2/include/components/kpb.conf | 76 + .../include/components/level_multiplier.conf | 65 + .../level_multiplier/gain_-10_db.conf | 10 + .../level_multiplier/gain_-20_db.conf | 10 + .../level_multiplier/gain_-30_db.conf | 10 + .../level_multiplier/gain_-40_db.conf | 10 + .../level_multiplier/gain_0_db.conf | 10 + .../level_multiplier/gain_10_db.conf | 10 + .../level_multiplier/gain_20_db.conf | 10 + .../level_multiplier/gain_30_db.conf | 10 + .../level_multiplier/gain_40_db.conf | 10 + .../topology2/include/components/mfcc.conf | 64 + .../include/components/mfcc/default.conf | 22 + .../topology2/include/components/micsel.conf | 73 + .../components/micsel/downmix_51_to_mono.conf | 26 + .../micsel/downmix_51_to_mono_with_lfe.conf | 26 + .../micsel/downmix_51_to_stereo.conf | 26 + .../micsel/downmix_51_to_stereo_with_lfe.conf | 26 + .../components/micsel/downmix_71_to_51.conf | 26 + .../components/micsel/downmix_71_to_mono.conf | 26 + .../micsel/downmix_71_to_mono_with_lfe.conf | 26 + .../micsel/downmix_71_to_stereo.conf | 26 + .../micsel/downmix_71_to_stereo_with_lfe.conf | 26 + .../micsel/downmix_stereo_to_mono.conf | 26 + .../components/micsel/passthrough.conf | 26 + .../components/micsel/upmix_mono_to_51.conf | 26 + .../components/micsel/upmix_mono_to_71.conf | 26 + .../micsel/upmix_mono_to_stereo.conf | 26 + .../components/micsel/upmix_stereo_to_51.conf | 26 + .../components/micsel/upmix_stereo_to_71.conf | 26 + .../topology2/include/components/mixin.conf | 47 +- .../topology2/include/components/mixout.conf | 46 +- .../include/components/module-copier.conf | 95 + .../include/components/multiband_drc.conf | 88 + .../components/multiband_drc/default.conf | 82 + .../components/multiband_drc/passthrough.conf | 71 + .../include/components/muxdemux.conf | 13 +- .../components/ov_noise_suppression.conf | 81 + .../include/components/pipeline.conf | 61 +- .../topology2/include/components/rtnr.conf | 86 + .../components/rtnr/default_bytes.conf | 13 + .../include/components/rtnr/default_data.conf | 9 + .../include/components/smart_amp.conf | 120 + .../include/components/sound_dose.conf | 67 + .../sound_dose/setup_data_init.conf | 9 + .../components/sound_dose/setup_gain_0db.conf | 10 + .../components/sound_dose/setup_sens_0db.conf | 10 + .../sound_dose/setup_sens_100db.conf | 10 + .../components/sound_dose/setup_vol_0db.conf | 10 + .../topology2/include/components/src.conf | 29 +- .../src_format_s16_convert_from_48k.conf | 43 + .../src_format_s16_convert_to_48k.conf | 35 + .../src_format_s16_passthrough.conf | 50 + .../src_format_s16_to_sxx_convert.conf | 73 + .../src_format_s24_convert_from_48k.conf | 43 + .../src_format_s24_convert_to_48k.conf | 35 + .../src_format_s24_passthrough.conf | 50 + .../src_format_s24_to_sxx_convert.conf | 73 + .../src_format_s32_convert_from_48k.conf | 44 + .../src_format_s32_convert_to_48k.conf | 35 + .../src_format_s32_passthrough.conf | 50 + .../src_format_s32_to_sxx_convert.conf | 73 + .../src_format_sxx_to_s16_convert.conf | 73 + .../src_format_sxx_to_s24_convert.conf | 73 + .../src_format_sxx_to_s32_convert.conf | 73 + .../include/components/src_lite.conf | 89 + .../topology2/include/components/tdfb.conf | 175 + .../tdfb/line2_50mm_azm90_90_13_16khz.conf | 362 + .../tdfb/line2_50mm_azm90_90_13_48khz.conf | 518 ++ .../tdfb/line2_50mm_pm0_30_90deg_16khz.conf | 258 + .../tdfb/line2_50mm_pm0_30_90deg_48khz.conf | 258 + .../tdfb/line2_50mm_pm90deg_16khz.conf | 93 + .../tdfb/line2_50mm_pm90deg_48khz.conf | 93 + .../tdfb/line2_generic_pm10deg_16khz.conf | 108 + .../tdfb/line2_generic_pm10deg_48khz.conf | 108 + .../include/components/tdfb/line2_pass.conf | 22 + .../tdfb/line4_28mm_azm90_90_13_16khz.conf | 698 ++ .../tdfb/line4_28mm_azm90_90_13_48khz.conf | 1218 +++ .../tdfb/line4_28mm_pm90deg_16khz.conf | 172 + .../tdfb/line4_28mm_pm90deg_48khz.conf | 172 + .../tdfb/line4_generic_pm10deg_16khz.conf | 202 + .../tdfb/line4_generic_pm10deg_48khz.conf | 202 + .../include/components/tdfb/line4_pass.conf | 31 + .../components/tdfb/line4to2_pass.conf | 22 + .../include/components/template_comp.conf | 86 + .../topology2/include/components/virtual.conf | 4 +- .../topology2/include/components/volume.conf | 40 +- .../include/components/widget-common.conf | 121 +- .../topology2/include/components/wov.conf | 104 + .../topology2/include/controls/bytes.conf | 6 +- .../topology2/include/controls/common.conf | 137 +- .../topology2/include/controls/enum.conf | 94 + .../topology2/include/controls/mixer.conf | 74 +- .../topology/topology2/include/dais/alh.conf | 19 +- .../topology/topology2/include/dais/dmic.conf | 30 +- .../topology/topology2/include/dais/hda.conf | 14 +- .../include/dais/hw_config_simple.conf | 35 + .../topology2/include/dais/mic_extension.conf | 3 + .../topology2/include/dais/pdm_config.conf | 20 +- .../topology/topology2/include/dais/sai.conf | 94 + .../topology/topology2/include/dais/ssp.conf | 44 +- .../include/pipelines/cavs/dai-copier-be.conf | 104 + ...ai-copier-eqiir-module-copier-capture.conf | 162 + ...pier-gain-eqiir-module-copier-capture.conf | 200 + .../cavs/dai-copier-gain-mixin-capture.conf | 102 + ...dai-copier-gain-module-copier-capture.conf | 117 + .../include/pipelines/cavs/dai-kpb-be.conf | 137 + .../pipelines/cavs/deepbuffer-playback.conf | 111 + .../pipelines/cavs/gain-copier-capture.conf | 148 + .../pipelines/cavs/gain-module-copier.conf | 111 + .../cavs/google-rtc-aec-capture.conf | 140 + .../pipelines/cavs/highpass-capture-be.conf | 88 + .../cavs/host-copier-gain-mixin-playback.conf | 110 + .../host-copier-gain-src-mixin-playback.conf | 86 + ...ost-copier-micsel-gain-mixin-playback.conf | 359 + .../pipelines/cavs/host-gateway-capture.conf | 109 + .../pipelines/cavs/host-gateway-playback.conf | 81 + .../cavs/host-gateway-tdfb-drc-capture.conf | 106 + .../pipelines/cavs/io-gateway-capture.conf | 65 + .../include/pipelines/cavs/io-gateway.conf | 92 + .../cavs/mixout-aria-gain-mixin-playback.conf | 92 + .../cavs/mixout-dai-copier-playback.conf | 75 + .../mixout-gain-alh-dai-copier-playback.conf | 105 + .../mixout-gain-ctc-dai-copier-playback.conf | 71 + .../cavs/mixout-gain-dai-copier-playback.conf | 102 + .../mixout-gain-efx-dai-copier-playback.conf | 74 + ...ut-gain-efx-mbdrc-dai-copier-playback.conf | 70 + ...ut-gain-eqiir-dts-dai-copier-playback.conf | 152 + ...iir-eqfir-drc-alh-dai-copier-playback.conf | 137 + .../mixout-gain-eqiir-eqfir-playback.conf | 102 + .../cavs/mixout-gain-host-copier-capture.conf | 120 + ...ut-gain-smart-amp-dai-copier-playback.conf | 132 + .../include/pipelines/cavs/mixout-mixin.conf | 59 + .../cavs/src-gain-mixin-playback.conf | 112 + .../include/pipelines/cavs/wov-detect.conf | 127 + .../pipelines/eq-iir-volume-capture.conf | 24 +- .../include/pipelines/gain-capture.conf | 225 + .../include/pipelines/gain-playback.conf | 106 + .../pipelines/passthrough-capture.conf | 8 +- .../include/pipelines/pipeline-common.conf | 27 +- .../include/pipelines/volume-capture.conf | 16 +- .../pipelines/volume-demux-playback.conf | 24 +- .../include/pipelines/volume-playback.conf | 60 +- .../topology2/platform/intel/bt-default.conf | 23 + .../topology2/platform/intel/bt-generic.conf | 252 + .../platform/intel/bt-ssp-config-lbm.conf | 65 + .../platform/intel/bt-ssp-config.conf | 70 + .../platform/intel/deep-buffer-default.conf | 8 + .../topology2/platform/intel/deep-buffer.conf | 184 + .../platform/intel/dmic-default.conf | 50 + .../platform/intel/dmic-generic.conf | 906 +++ .../topology2/platform/intel/dmic-wov.conf | 147 + .../topology2/platform/intel/dmic1-mfcc.conf | 573 ++ .../platform/intel/dmic1-passthrough.conf | 237 + .../topology2/platform/intel/efx-default.conf | 8 + .../intel/google-rtc-aec-reference.conf | 118 + .../platform/intel/hdmi-default.conf | 28 + .../platform/intel/hdmi-generic.conf | 295 + .../platform/intel/hdmi-in-capture.conf | 180 + .../platform/intel/hdmi-in-default.conf | 28 + .../platform/intel/hdmi-in-ssp-config.conf | 56 + .../intel/hw_config_cardinal_clk.conf | 107 + .../topology2/platform/intel/lnl.conf | 9 + .../topology2/platform/intel/mtl.conf | 9 + .../platform/intel/nocodec-ssp0-2level.conf | 66 + .../topology2/platform/intel/ptl.conf | 9 + .../platform/intel/sdw-amp-generic.conf | 834 ++ .../platform/intel/sdw-dmic-generic.conf | 231 + .../platform/intel/sdw-jack-generic.conf | 500 ++ .../platform/intel/speaker-echo-ref.conf | 51 + .../topology2/platform/intel/ssp-default.conf | 12 + .../platform/intel/ssp_aux_config.conf | 282 + .../topology2/platform/intel/tgl.conf | 4 + .../topology2/production/CMakeLists.txt | 33 + .../production/tplg-targets-ace1.cmake | 348 + .../production/tplg-targets-ace2.cmake | 141 + .../production/tplg-targets-ace3.cmake | 150 + .../production/tplg-targets-cavs25.cmake | 56 + .../production/tplg-targets-hda-generic.cmake | 101 + .../production/tplg-targets-imx8.cmake | 7 + .../tplg-targets-sdca-generic.cmake | 30 + tools/topology/topology2/sof-hda-generic.conf | 184 + tools/tplg_parser/CMakeLists.txt | 49 +- tools/tplg_parser/asrc.c | 107 + tools/tplg_parser/audio_formats.c | 163 + tools/tplg_parser/buffer.c | 98 + tools/tplg_parser/control.c | 302 +- tools/tplg_parser/dai.c | 155 +- tools/tplg_parser/graph.c | 115 + tools/tplg_parser/host.c | 79 + tools/tplg_parser/include/linux/types.h | 22 + .../tplg_parser/include/tplg_parser/tokens.h | 69 + .../include/tplg_parser/topology.h | 385 +- tools/tplg_parser/mixer.c | 114 +- tools/tplg_parser/object.c | 91 + tools/tplg_parser/pcm.c | 116 +- tools/tplg_parser/pga.c | 206 +- tools/tplg_parser/pipeline.c | 353 +- tools/tplg_parser/process.c | 583 +- tools/tplg_parser/src.c | 98 + tools/tplg_parser/tokens.c | 214 +- tools/tune/common/sof_alsactl_write.m | 33 + tools/tune/common/sof_blob_write.m | 30 + tools/tune/common/sof_check_blob_header.m | 37 + tools/tune/common/sof_check_create_dir.m | 19 + tools/tune/common/sof_get_abi.m | 73 + tools/tune/common/sof_tplg2_write.m | 62 + tools/tune/common/sof_tplg_write.m | 55 + tools/tune/common/sof_ucm_blob_write.m | 42 + tools/tune/dmic/dmic_batch.m | 34 +- tools/tune/dmic/dmic_fir_export_alsa.m | 102 + tools/tune/dmic/dmic_init.m | 64 +- tools/tune/mfcc/README.txt | 29 + tools/tune/mfcc/decode_ceps.m | 93 + tools/tune/mfcc/run_mfcc.sh | 23 + tools/tune/mfcc/setup_mfcc.m | 210 + tools/tune/tdfb/example_all.sh | 0 759 files changed, 83629 insertions(+), 4873 deletions(-) create mode 100644 tools/corpus/README.md create mode 100644 tools/corpus/sof-ipc3/DELETE_ME create mode 100644 tools/corpus/sof-ipc4/DELETE_ME create mode 100644 tools/ctl/ipc3/eq_fir/flat.txt create mode 100644 tools/ctl/ipc3/eq_fir/loudness.txt create mode 100644 tools/ctl/ipc3/eq_fir/mid.txt create mode 100644 tools/ctl/ipc3/eq_fir/pass.txt create mode 100644 tools/ctl/ipc3/eq_iir/bandpass.txt create mode 100644 tools/ctl/ipc3/eq_iir/bassboost.txt create mode 100644 tools/ctl/ipc3/eq_iir/bundle.txt create mode 100644 tools/ctl/ipc3/eq_iir/flat.txt create mode 100644 tools/ctl/ipc3/eq_iir/highpass_20hz_0db_48khz.txt create mode 100644 tools/ctl/ipc3/eq_iir/highpass_30hz_0db_48khz.txt create mode 100644 tools/ctl/ipc3/eq_iir/highpass_40hz_0db_48khz.txt create mode 100644 tools/ctl/ipc3/eq_iir/highpass_50hz_0db_48khz.txt create mode 100644 tools/ctl/ipc3/eq_iir/loudness.txt create mode 100644 tools/ctl/ipc3/eq_iir/pass.txt create mode 100644 tools/ctl/ipc4/drc/generic_notebook_speaker.txt create mode 100644 tools/ctl/ipc4/drc/passthrough.txt create mode 100644 tools/ctl/ipc4/eq_fir/flat.txt create mode 100644 tools/ctl/ipc4/eq_fir/loudness.txt create mode 100644 tools/ctl/ipc4/eq_fir/mid.txt create mode 100644 tools/ctl/ipc4/eq_fir/pass.txt create mode 100644 tools/ctl/ipc4/eq_iir/bandpass.txt create mode 100644 tools/ctl/ipc4/eq_iir/bassboost.txt create mode 100644 tools/ctl/ipc4/eq_iir/flat.txt create mode 100644 tools/ctl/ipc4/eq_iir/highpass_20hz_0db_48khz.txt create mode 100644 tools/ctl/ipc4/eq_iir/highpass_30hz_0db_48khz.txt create mode 100644 tools/ctl/ipc4/eq_iir/highpass_40hz_0db_48khz.txt create mode 100644 tools/ctl/ipc4/eq_iir/highpass_40hz_20db_48khz.txt create mode 100644 tools/ctl/ipc4/eq_iir/highpass_40hz_30db_48khz.txt create mode 100644 tools/ctl/ipc4/eq_iir/highpass_40hz_40db_48khz.txt create mode 100644 tools/ctl/ipc4/eq_iir/highpass_50hz_0db_48khz.txt create mode 100644 tools/ctl/ipc4/eq_iir/loudness.txt create mode 100644 tools/ctl/ipc4/eq_iir/pass.txt create mode 100644 tools/debug_stream/debug_stream.py create mode 100644 tools/demo-gui/README-dev.md create mode 100644 tools/demo-gui/README.md create mode 100644 tools/demo-gui/demo-gui/sof_controller_engine.py create mode 100644 tools/demo-gui/demo-gui/sof_demo_gui.py create mode 100644 tools/demo-gui/demo-gui/sof_demo_tui.py create mode 100644 tools/logger/config.h.in create mode 100644 tools/mtrace/mtrace-reader.c create mode 100644 tools/mtrace/mtrace-reader.py create mode 100644 tools/plugin/CMakeLists.txt create mode 100644 tools/plugin/README.md create mode 100644 tools/plugin/alsaconf/50-sof.conf create mode 100644 tools/plugin/alsaconf/CMakeLists.txt create mode 100644 tools/plugin/alsaplug/CMakeLists.txt create mode 100644 tools/plugin/alsaplug/conf.c create mode 100644 tools/plugin/alsaplug/ctl.c create mode 100644 tools/plugin/alsaplug/pcm.c create mode 100644 tools/plugin/alsaplug/plugin.c create mode 100644 tools/plugin/alsaplug/plugin.h create mode 100644 tools/plugin/alsaplug/tplg.c create mode 100644 tools/plugin/alsaplug/tplg_ctl.c create mode 100644 tools/plugin/common.c create mode 100644 tools/plugin/common.h create mode 100644 tools/plugin/modules/CMakeLists.txt create mode 100644 tools/plugin/modules/alsa.c create mode 100644 tools/plugin/modules/ov_noise_suppression/CMakeLists.txt create mode 100644 tools/plugin/modules/ov_noise_suppression/noise_suppression.c create mode 100644 tools/plugin/modules/ov_noise_suppression/noise_suppression_interface.cpp create mode 100644 tools/plugin/modules/ov_noise_suppression/noise_suppression_interface.h create mode 100644 tools/plugin/modules/shm.c create mode 100644 tools/plugin/pipe/CMakeLists.txt create mode 100644 tools/plugin/pipe/cpu.c create mode 100644 tools/plugin/pipe/ipc4.c create mode 100644 tools/plugin/pipe/main.c create mode 100644 tools/plugin/pipe/pipe.h create mode 100644 tools/plugin/pipe/pipeline.c create mode 100644 tools/probes/probes_demux.c create mode 100644 tools/probes/probes_demux.h create mode 100644 tools/rimage/.checkpatch.conf create mode 100644 tools/rimage/CMakeLists.txt create mode 100644 tools/rimage/LICENSE create mode 100644 tools/rimage/README.md create mode 100644 tools/rimage/config/acp_6_0.toml create mode 100644 tools/rimage/config/acp_6_3.toml create mode 100644 tools/rimage/config/acp_7_0.toml create mode 100644 tools/rimage/config/apl.toml create mode 100644 tools/rimage/config/bdw.toml create mode 100644 tools/rimage/config/bsw.toml create mode 100644 tools/rimage/config/byt.toml create mode 100644 tools/rimage/config/cht.toml create mode 100644 tools/rimage/config/cnl.toml create mode 100644 tools/rimage/config/hsw.toml create mode 100644 tools/rimage/config/icl.toml create mode 100644 tools/rimage/config/imx8.toml create mode 100644 tools/rimage/config/imx8m.toml create mode 100644 tools/rimage/config/imx8ulp.toml create mode 100644 tools/rimage/config/imx8x.toml create mode 100644 tools/rimage/config/imx95.toml create mode 100644 tools/rimage/config/jsl.toml create mode 100644 tools/rimage/config/kbl.toml create mode 100644 tools/rimage/config/lnl.toml.h create mode 100644 tools/rimage/config/mt8186.toml create mode 100644 tools/rimage/config/mt8188.toml create mode 100644 tools/rimage/config/mt8195.toml create mode 100644 tools/rimage/config/mt8196.toml create mode 100644 tools/rimage/config/mt8365.toml create mode 100644 tools/rimage/config/mtl.toml.h create mode 100644 tools/rimage/config/nvl.toml.h create mode 100644 tools/rimage/config/platform-lnl.toml create mode 100644 tools/rimage/config/platform-mtl.toml create mode 100644 tools/rimage/config/platform-nvl.toml create mode 100644 tools/rimage/config/platform-ptl.toml create mode 100644 tools/rimage/config/platform.toml create mode 100644 tools/rimage/config/ptl.toml.h create mode 100644 tools/rimage/config/rmb.toml create mode 100644 tools/rimage/config/rn.toml create mode 100644 tools/rimage/config/skl.toml create mode 100644 tools/rimage/config/sue.toml create mode 100644 tools/rimage/config/tgl-h.toml create mode 100644 tools/rimage/config/tgl.toml create mode 100644 tools/rimage/config/vangogh.toml create mode 100644 tools/rimage/config/wcl.toml.h create mode 100644 tools/rimage/scripts/checkpatch.pl create mode 100644 tools/rimage/scripts/const_structs.checkpatch create mode 100644 tools/rimage/scripts/spelling.txt create mode 100644 tools/rimage/src/adsp_config.c create mode 100644 tools/rimage/src/cse.c create mode 100644 tools/rimage/src/css.c create mode 100644 tools/rimage/src/elf_file.c create mode 100644 tools/rimage/src/ext_manifest.c create mode 100644 tools/rimage/src/file_simple.c create mode 100644 tools/rimage/src/file_utils.c create mode 100644 tools/rimage/src/hash.c create mode 100644 tools/rimage/src/include/rimage/adsp_config.h create mode 100644 tools/rimage/src/include/rimage/cavs/cavs_ext_manifest.h create mode 100644 tools/rimage/src/include/rimage/cse.h create mode 100644 tools/rimage/src/include/rimage/css.h create mode 100644 tools/rimage/src/include/rimage/elf.h create mode 100644 tools/rimage/src/include/rimage/elf_file.h create mode 100644 tools/rimage/src/include/rimage/ext_manifest_gen.h create mode 100644 tools/rimage/src/include/rimage/file_utils.h create mode 100644 tools/rimage/src/include/rimage/hash.h create mode 100644 tools/rimage/src/include/rimage/manifest.h create mode 100644 tools/rimage/src/include/rimage/misc_utils.h create mode 100644 tools/rimage/src/include/rimage/module.h create mode 100644 tools/rimage/src/include/rimage/plat_auth.h create mode 100644 tools/rimage/src/include/rimage/rimage.h create mode 100644 tools/rimage/src/include/rimage/sof/kernel/ext_manifest.h create mode 100644 tools/rimage/src/include/rimage/sof/kernel/fw.h create mode 100644 tools/rimage/src/include/rimage/sof/user/manifest.h create mode 100644 tools/rimage/src/include/rimage/toml_utils.h create mode 100644 tools/rimage/src/manifest.c create mode 100644 tools/rimage/src/misc_utils.c create mode 100644 tools/rimage/src/module.c create mode 100644 tools/rimage/src/pkcs1_5.c create mode 100644 tools/rimage/src/plat_auth.c create mode 100644 tools/rimage/src/rimage.c create mode 100644 tools/rimage/src/toml_utils.c create mode 100644 tools/test/audio/README.md create mode 100644 tools/test/audio/std_utils/ldlg_test_input.m create mode 100644 tools/test/audio/std_utils/ldlg_test_measure.m create mode 100644 tools/test/audio/tdfb_enable.sh create mode 100644 tools/test/topology/platform/generic.m4 create mode 100644 tools/testbench/README.md create mode 100644 tools/testbench/include/linux/types.h create mode 100644 tools/testbench/include/testbench/file_ipc4.h create mode 100644 tools/testbench/include/testbench/topology_ipc4.h create mode 100644 tools/testbench/include/testbench/utils.h create mode 100644 tools/testbench/testbench_xcc_sections.txt create mode 100644 tools/testbench/topology_ipc3.c create mode 100644 tools/testbench/topology_ipc4.c create mode 100644 tools/testbench/utils.c create mode 100644 tools/testbench/utils_ipc3.c create mode 100644 tools/testbench/utils_ipc4.c create mode 100644 tools/topology/topology1/m4/crossover/coef_2way_48000_200_1_2.m4 create mode 100644 tools/topology/topology1/m4/crossover/coef_3way_48000_200_1000_1_2_3.m4 create mode 100644 tools/topology/topology1/m4/crossover/coef_4way_48000_200_1000_3000_1_2_3_4.m4 create mode 100644 tools/topology/topology1/m4/drc_coef_speaker_default.m4 create mode 100644 tools/topology/topology1/m4/eap.m4 create mode 100644 tools/topology/topology1/m4/eap_controls.m4 create mode 100644 tools/topology/topology1/m4/google_ctc_audio_processing.m4 create mode 100644 tools/topology/topology1/m4/google_ctc_audio_processing_coef_default.m4 create mode 100644 tools/topology/topology1/m4/mfcc.m4 create mode 100644 tools/topology/topology1/m4/mfcc/mfcc_config.m4 create mode 100644 tools/topology/topology1/m4/multiband_drc_coef_passthrough.m4 create mode 100644 tools/topology/topology1/platform/common/acp-hs.m4 create mode 100644 tools/topology/topology1/platform/common/acp-sdw.m4 create mode 100644 tools/topology/topology1/platform/common/micfil.m4 create mode 100644 tools/topology/topology1/platform/mediatek/mt8188.m4 create mode 100644 tools/topology/topology1/platform/mediatek/mt8196.m4 create mode 100644 tools/topology/topology1/platform/mediatek/mt8365.m4 create mode 100644 tools/topology/topology1/sof-acp-rmb-dmic4ch.m4 create mode 100644 tools/topology/topology1/sof-acp-rmb-tdm8ch-dmic2ch-nau8825-max98360.m4 create mode 100644 tools/topology/topology1/sof-acp-rmb-tdm8ch-dmic4ch-rt5682-rt1019.m4 create mode 100644 tools/topology/topology1/sof-acp-rmb.m4 create mode 100644 tools/topology/topology1/sof-acp-vangogh.m4 create mode 100644 tools/topology/topology1/sof-acp_6_3.m4 create mode 100644 tools/topology/topology1/sof-acp_6_3_sdw.m4 create mode 100644 tools/topology/topology1/sof-acp_7_0.m4 create mode 100644 tools/topology/topology1/sof-acp_7_0_sdw.m4 create mode 100644 tools/topology/topology1/sof-imx8-compr-pcm-cap-wm8960.m4 create mode 100644 tools/topology/topology1/sof-imx8-compr-pcm-wm8960.m4 create mode 100644 tools/topology/topology1/sof-imx8-compr-wm8960.m4 create mode 100644 tools/topology/topology1/sof-imx8-src-cs42888.m4 create mode 100644 tools/topology/topology1/sof-imx8-src-wm8960.m4 create mode 100644 tools/topology/topology1/sof-imx8-wm8960-cs42888.m4 create mode 100644 tools/topology/topology1/sof-imx8mp-btsco-dual-8ch.m4 create mode 100644 tools/topology/topology1/sof-imx8mp-compr-pcm-cap-wm8960.m4 create mode 100644 tools/topology/topology1/sof-imx8mp-compr-pcm-wm8960.m4 create mode 100644 tools/topology/topology1/sof-imx8mp-compr-wm8960.m4 create mode 100644 tools/topology/topology1/sof-imx8mp-micfil.m4 create mode 100644 tools/topology/topology1/sof-imx93-wm8962.m4 create mode 100644 tools/topology/topology1/sof-imx95-wm8962.m4 create mode 100644 tools/topology/topology1/sof-mt8188-mt6359.m4 create mode 100644 tools/topology/topology1/sof-mt8196-mt6681.m4 create mode 100644 tools/topology/topology1/sof-mt8365-mt6357.m4 create mode 100644 tools/topology/topology1/sof/pipe-ctc-playback.m4 create mode 100644 tools/topology/topology1/sof/pipe-dts-codec-eq-iir-playback.m4 create mode 100644 tools/topology/topology1/sof/pipe-dts-codec-playback.m4 create mode 100644 tools/topology/topology1/sof/pipe-eap-playback.m4 create mode 100644 tools/topology/topology1/sof/pipe-eq-iir-mfcc-capture-16khz.m4 create mode 100644 tools/topology/topology1/sof/pipe-gui-components-capture.m4 create mode 100644 tools/topology/topology1/sof/pipe-gui-components-playback.m4 create mode 100644 tools/topology/topology1/sof/pipe-host-playback.m4 create mode 100644 tools/topology/topology1/sof/pipe-host-src-volume-playback.m4 create mode 100644 tools/topology/topology1/sof/pipe-low-latency-capture-demux.m4 create mode 100644 tools/topology/topology1/sof/pipe-low-latency-playback_mux_tdm4ch.m4 create mode 100644 tools/topology/topology1/sof/pipe-mfcc-capture.m4 create mode 100644 tools/topology/topology1/sof/pipe-mfcc-playback.m4 create mode 100644 tools/topology/topology1/sof/pipe-mixer-eq-iir-volume-dai-playback.m4 create mode 100644 tools/topology/topology1/sof/pipe-mux-dai-playback.m4 create mode 100644 tools/topology/topology1/sof/pipe-pcm-mux.m4 create mode 100644 tools/topology/topology1/sof/pipe-virtual-passthrough-playback-sched.m4 create mode 100644 tools/topology/topology1/sof/pipe-virtual-playback-passthrough.m4 create mode 100644 tools/topology/topology1/sof/pipe-waves-codec-ctc-playback.m4 create mode 100644 tools/topology/topology2/cavs-benchmark-hda.conf create mode 100644 tools/topology/topology2/cavs-benchmark-sdw.conf create mode 100644 tools/topology/topology2/cavs-cs35l56.conf create mode 100644 tools/topology/topology2/cavs-es83x6.conf create mode 100644 tools/topology/topology2/cavs-mixin-mixout-ctc-ssp.conf create mode 100644 tools/topology/topology2/cavs-mixin-mixout-efx-hda.conf create mode 100644 tools/topology/topology2/cavs-mixin-mixout-eqiir-dts-ssp.conf create mode 100644 tools/topology/topology2/cavs-mixin-mixout-hda.conf create mode 100644 tools/topology/topology2/cavs-mixin-mixout-ssp.conf create mode 100644 tools/topology/topology2/cavs-nocodec-bt.conf create mode 100644 tools/topology/topology2/cavs-nocodec.conf create mode 100644 tools/topology/topology2/cavs-passthrough-hda.conf create mode 100644 tools/topology/topology2/cavs-rt5682.conf create mode 100644 tools/topology/topology2/cavs-sdw-src-gain-mixin.conf create mode 100644 tools/topology/topology2/cavs-sdw.conf create mode 100644 tools/topology/topology2/cavs-src-mixin-mixout-hda.conf create mode 100644 tools/topology/topology2/development/CMakeLists.txt create mode 100644 tools/topology/topology2/development/cavs-nocodec-crossover.conf create mode 100644 tools/topology/topology2/development/cavs-nocodec-rtcaec.conf create mode 100644 tools/topology/topology2/development/sof-plugin.conf create mode 100644 tools/topology/topology2/development/tplg-targets-bench.cmake create mode 100644 tools/topology/topology2/development/tplg-targets.cmake create mode 100644 tools/topology/topology2/doc/CMakeLists.txt create mode 100644 tools/topology/topology2/doc/README create mode 100644 tools/topology/topology2/doc/extra-contents/mainpage.doxy create mode 100644 tools/topology/topology2/doc/sof.doxygen.in create mode 100644 tools/topology/topology2/doc/topology2-filter.py create mode 100644 tools/topology/topology2/doc/topology2-generate-contents.sh create mode 100644 tools/topology/topology2/imx8-wm8960.conf create mode 100644 tools/topology/topology2/include/bench/aria_controls_capture.conf create mode 100644 tools/topology/topology2/include/bench/aria_controls_playback.conf create mode 100644 tools/topology/topology2/include/bench/aria_route.conf create mode 100644 tools/topology/topology2/include/bench/aria_s24.conf create mode 100644 tools/topology/topology2/include/bench/asrc_route.conf create mode 100644 tools/topology/topology2/include/bench/asrc_s16.conf create mode 100644 tools/topology/topology2/include/bench/asrc_s24.conf create mode 100644 tools/topology/topology2/include/bench/asrc_s32.conf create mode 100644 tools/topology/topology2/include/bench/bench_comp_generate.sh create mode 100644 tools/topology/topology2/include/bench/dcblock_control_bytes_capture.conf create mode 100644 tools/topology/topology2/include/bench/dcblock_control_bytes_playback.conf create mode 100644 tools/topology/topology2/include/bench/dcblock_route.conf create mode 100644 tools/topology/topology2/include/bench/dcblock_s16.conf create mode 100644 tools/topology/topology2/include/bench/dcblock_s24.conf create mode 100644 tools/topology/topology2/include/bench/dcblock_s32.conf create mode 100644 tools/topology/topology2/include/bench/drc_controls_capture.conf create mode 100644 tools/topology/topology2/include/bench/drc_controls_playback.conf create mode 100644 tools/topology/topology2/include/bench/drc_route.conf create mode 100644 tools/topology/topology2/include/bench/drc_s16.conf create mode 100644 tools/topology/topology2/include/bench/drc_s24.conf create mode 100644 tools/topology/topology2/include/bench/drc_s32.conf create mode 100644 tools/topology/topology2/include/bench/eqfir_control_bytes_capture.conf create mode 100644 tools/topology/topology2/include/bench/eqfir_control_bytes_playback.conf create mode 100644 tools/topology/topology2/include/bench/eqfir_route.conf create mode 100644 tools/topology/topology2/include/bench/eqfir_s16.conf create mode 100644 tools/topology/topology2/include/bench/eqfir_s24.conf create mode 100644 tools/topology/topology2/include/bench/eqfir_s32.conf create mode 100644 tools/topology/topology2/include/bench/eqiir_control_bytes_capture.conf create mode 100644 tools/topology/topology2/include/bench/eqiir_control_bytes_playback.conf create mode 100644 tools/topology/topology2/include/bench/eqiir_route.conf create mode 100644 tools/topology/topology2/include/bench/eqiir_s16.conf create mode 100644 tools/topology/topology2/include/bench/eqiir_s24.conf create mode 100644 tools/topology/topology2/include/bench/eqiir_s32.conf create mode 100644 tools/topology/topology2/include/bench/gain_controls_capture.conf create mode 100644 tools/topology/topology2/include/bench/gain_controls_playback.conf create mode 100644 tools/topology/topology2/include/bench/gain_route.conf create mode 100644 tools/topology/topology2/include/bench/gain_s16.conf create mode 100644 tools/topology/topology2/include/bench/gain_s24.conf create mode 100644 tools/topology/topology2/include/bench/gain_s32.conf create mode 100644 tools/topology/topology2/include/bench/host_gateway_pipelines_multich_s32.conf create mode 100644 tools/topology/topology2/include/bench/host_gateway_pipelines_s16.conf create mode 100644 tools/topology/topology2/include/bench/host_gateway_pipelines_s24.conf create mode 100644 tools/topology/topology2/include/bench/host_gateway_pipelines_s32.conf create mode 100644 tools/topology/topology2/include/bench/host_gateway_pipelines_src_s16.conf create mode 100644 tools/topology/topology2/include/bench/host_gateway_pipelines_src_s24.conf create mode 100644 tools/topology/topology2/include/bench/host_gateway_pipelines_src_s32.conf create mode 100644 tools/topology/topology2/include/bench/host_io_gateway_pipelines_s16.conf create mode 100644 tools/topology/topology2/include/bench/host_io_gateway_pipelines_s24.conf create mode 100644 tools/topology/topology2/include/bench/host_io_gateway_pipelines_s32.conf create mode 100644 tools/topology/topology2/include/bench/igo_nr_controls_capture.conf create mode 100644 tools/topology/topology2/include/bench/igo_nr_controls_playback.conf create mode 100644 tools/topology/topology2/include/bench/igo_nr_route.conf create mode 100644 tools/topology/topology2/include/bench/igo_nr_s16.conf create mode 100644 tools/topology/topology2/include/bench/igo_nr_s24.conf create mode 100644 tools/topology/topology2/include/bench/igo_nr_s32.conf create mode 100644 tools/topology/topology2/include/bench/level_multiplier_controls_capture.conf create mode 100644 tools/topology/topology2/include/bench/level_multiplier_controls_playback.conf create mode 100644 tools/topology/topology2/include/bench/level_multiplier_route.conf create mode 100644 tools/topology/topology2/include/bench/level_multiplier_s16.conf create mode 100644 tools/topology/topology2/include/bench/level_multiplier_s24.conf create mode 100644 tools/topology/topology2/include/bench/level_multiplier_s32.conf create mode 100644 tools/topology/topology2/include/bench/micsel_controls_capture.conf create mode 100644 tools/topology/topology2/include/bench/micsel_controls_playback.conf create mode 100644 tools/topology/topology2/include/bench/micsel_hda_route.conf create mode 100644 tools/topology/topology2/include/bench/micsel_multich_s32.conf create mode 100644 tools/topology/topology2/include/bench/micsel_route.conf create mode 100644 tools/topology/topology2/include/bench/micsel_s16.conf create mode 100644 tools/topology/topology2/include/bench/micsel_s24.conf create mode 100644 tools/topology/topology2/include/bench/micsel_s32.conf create mode 100644 tools/topology/topology2/include/bench/multiband_drc_controls_capture.conf create mode 100644 tools/topology/topology2/include/bench/multiband_drc_controls_playback.conf create mode 100644 tools/topology/topology2/include/bench/multiband_drc_route.conf create mode 100644 tools/topology/topology2/include/bench/multiband_drc_s16.conf create mode 100644 tools/topology/topology2/include/bench/multiband_drc_s24.conf create mode 100644 tools/topology/topology2/include/bench/multiband_drc_s32.conf create mode 100644 tools/topology/topology2/include/bench/one_input_output_format_s16.conf create mode 100644 tools/topology/topology2/include/bench/one_input_output_format_s24.conf create mode 100644 tools/topology/topology2/include/bench/one_input_output_format_s32.conf create mode 100644 tools/topology/topology2/include/bench/rtnr_controls_capture.conf create mode 100644 tools/topology/topology2/include/bench/rtnr_controls_playback.conf create mode 100644 tools/topology/topology2/include/bench/rtnr_route.conf create mode 100644 tools/topology/topology2/include/bench/rtnr_s16.conf create mode 100644 tools/topology/topology2/include/bench/rtnr_s24.conf create mode 100644 tools/topology/topology2/include/bench/rtnr_s32.conf create mode 100644 tools/topology/topology2/include/bench/sound_dose_controls_capture.conf create mode 100644 tools/topology/topology2/include/bench/sound_dose_controls_playback.conf create mode 100644 tools/topology/topology2/include/bench/sound_dose_route.conf create mode 100644 tools/topology/topology2/include/bench/sound_dose_s16.conf create mode 100644 tools/topology/topology2/include/bench/sound_dose_s24.conf create mode 100644 tools/topology/topology2/include/bench/sound_dose_s32.conf create mode 100644 tools/topology/topology2/include/bench/src_lite_route.conf create mode 100644 tools/topology/topology2/include/bench/src_lite_s16.conf create mode 100644 tools/topology/topology2/include/bench/src_lite_s24.conf create mode 100644 tools/topology/topology2/include/bench/src_lite_s32.conf create mode 100644 tools/topology/topology2/include/bench/src_route.conf create mode 100644 tools/topology/topology2/include/bench/src_s16.conf create mode 100644 tools/topology/topology2/include/bench/src_s24.conf create mode 100644 tools/topology/topology2/include/bench/src_s32.conf create mode 100644 tools/topology/topology2/include/bench/tdfb_controls_capture.conf create mode 100644 tools/topology/topology2/include/bench/tdfb_controls_playback.conf create mode 100644 tools/topology/topology2/include/bench/tdfb_route.conf create mode 100644 tools/topology/topology2/include/bench/tdfb_s16.conf create mode 100644 tools/topology/topology2/include/bench/tdfb_s24.conf create mode 100644 tools/topology/topology2/include/bench/tdfb_s32.conf create mode 100644 tools/topology/topology2/include/bench/template_comp_controls_capture.conf create mode 100644 tools/topology/topology2/include/bench/template_comp_controls_playback.conf create mode 100644 tools/topology/topology2/include/bench/template_comp_route.conf create mode 100644 tools/topology/topology2/include/bench/template_comp_s16.conf create mode 100644 tools/topology/topology2/include/bench/template_comp_s24.conf create mode 100644 tools/topology/topology2/include/bench/template_comp_s32.conf create mode 100644 tools/topology/topology2/include/common/common_definitions.conf create mode 100644 tools/topology/topology2/include/common/input_audio_format.conf create mode 100644 tools/topology/topology2/include/common/input_pin_binding.conf create mode 100644 tools/topology/topology2/include/common/output_audio_format.conf create mode 100644 tools/topology/topology2/include/common/output_pin_binding.conf create mode 100644 tools/topology/topology2/include/components/alh-dai-copier.conf create mode 100644 tools/topology/topology2/include/components/aria.conf create mode 100644 tools/topology/topology2/include/components/aria/param_1.conf create mode 100644 tools/topology/topology2/include/components/aria/param_2.conf create mode 100644 tools/topology/topology2/include/components/aria/param_3.conf create mode 100644 tools/topology/topology2/include/components/aria/passthrough.conf create mode 100644 tools/topology/topology2/include/components/crossover.conf create mode 100644 tools/topology/topology2/include/components/crossover/coef_2way_48000_200_0_1.conf create mode 100644 tools/topology/topology2/include/components/crossover/coef_3way_48000_200_1000_0_1_2.conf create mode 100644 tools/topology/topology2/include/components/crossover/coef_4way_48000_200_1000_3000_0_1_2_3.conf create mode 100644 tools/topology/topology2/include/components/ctc.conf create mode 100644 tools/topology/topology2/include/components/dai-copier.conf create mode 100644 tools/topology/topology2/include/components/dcblock.conf create mode 100644 tools/topology/topology2/include/components/dcblock/100hz_16khz.conf create mode 100644 tools/topology/topology2/include/components/dcblock/100hz_48khz.conf create mode 100644 tools/topology/topology2/include/components/dcblock/200hz_16khz.conf create mode 100644 tools/topology/topology2/include/components/dcblock/200hz_48khz.conf create mode 100644 tools/topology/topology2/include/components/dcblock/20hz_16khz.conf create mode 100644 tools/topology/topology2/include/components/dcblock/20hz_48khz.conf create mode 100644 tools/topology/topology2/include/components/dcblock/30hz_16khz.conf create mode 100644 tools/topology/topology2/include/components/dcblock/30hz_48khz.conf create mode 100644 tools/topology/topology2/include/components/dcblock/40hz_16khz.conf create mode 100644 tools/topology/topology2/include/components/dcblock/40hz_48khz.conf create mode 100644 tools/topology/topology2/include/components/dcblock/50hz_16khz.conf create mode 100644 tools/topology/topology2/include/components/dcblock/50hz_48khz.conf create mode 100644 tools/topology/topology2/include/components/dcblock/80hz_16khz.conf create mode 100644 tools/topology/topology2/include/components/dcblock/80hz_48khz.conf create mode 100644 tools/topology/topology2/include/components/dcblock/default.conf create mode 100644 tools/topology/topology2/include/components/drc.conf create mode 100644 tools/topology/topology2/include/components/drc/default.conf create mode 100644 tools/topology/topology2/include/components/drc/dmic_default.conf create mode 100644 tools/topology/topology2/include/components/drc/enabled.conf create mode 100644 tools/topology/topology2/include/components/drc/passthrough.conf create mode 100644 tools/topology/topology2/include/components/drc/speaker_default.conf create mode 100644 tools/topology/topology2/include/components/dts.conf create mode 100644 tools/topology/topology2/include/components/dts/dts_spk.conf create mode 100644 tools/topology/topology2/include/components/eqfir.conf create mode 100644 tools/topology/topology2/include/components/eqfir/flat.conf create mode 100644 tools/topology/topology2/include/components/eqfir/loudness.conf create mode 100644 tools/topology/topology2/include/components/eqfir/midboost.conf create mode 100644 tools/topology/topology2/include/components/eqfir/passthrough.conf create mode 100644 tools/topology/topology2/include/components/eqiir/bandpass.conf create mode 100644 tools/topology/topology2/include/components/eqiir/bassboost.conf create mode 100644 tools/topology/topology2/include/components/eqiir/flat.conf create mode 100644 tools/topology/topology2/include/components/eqiir/highpass_100hz_0db_16khz.conf create mode 100644 tools/topology/topology2/include/components/eqiir/highpass_100hz_0db_48khz.conf create mode 100644 tools/topology/topology2/include/components/eqiir/highpass_100hz_20db_16khz.conf create mode 100644 tools/topology/topology2/include/components/eqiir/highpass_100hz_20db_48khz.conf create mode 100644 tools/topology/topology2/include/components/eqiir/highpass_20hz_0db_16khz.conf create mode 100644 tools/topology/topology2/include/components/eqiir/highpass_20hz_0db_48khz.conf create mode 100644 tools/topology/topology2/include/components/eqiir/highpass_20hz_20db_16khz.conf create mode 100644 tools/topology/topology2/include/components/eqiir/highpass_20hz_20db_48khz.conf create mode 100644 tools/topology/topology2/include/components/eqiir/highpass_30hz_0db_16khz.conf create mode 100644 tools/topology/topology2/include/components/eqiir/highpass_30hz_0db_48khz.conf create mode 100644 tools/topology/topology2/include/components/eqiir/highpass_30hz_20db_16khz.conf create mode 100644 tools/topology/topology2/include/components/eqiir/highpass_30hz_20db_48khz.conf create mode 100644 tools/topology/topology2/include/components/eqiir/highpass_40hz_0db_16khz.conf create mode 100644 tools/topology/topology2/include/components/eqiir/highpass_40hz_0db_48khz.conf create mode 100644 tools/topology/topology2/include/components/eqiir/highpass_40hz_20db_16khz.conf create mode 100644 tools/topology/topology2/include/components/eqiir/highpass_40hz_20db_48khz.conf create mode 100644 tools/topology/topology2/include/components/eqiir/highpass_50hz_0db_16khz.conf create mode 100644 tools/topology/topology2/include/components/eqiir/highpass_50hz_0db_48khz.conf create mode 100644 tools/topology/topology2/include/components/eqiir/highpass_50hz_20db_16khz.conf create mode 100644 tools/topology/topology2/include/components/eqiir/highpass_50hz_20db_48khz.conf create mode 100644 tools/topology/topology2/include/components/eqiir/loudness.conf create mode 100644 tools/topology/topology2/include/components/eqiir/passthrough.conf create mode 100644 tools/topology/topology2/include/components/google-rtc-aec.conf create mode 100644 tools/topology/topology2/include/components/host-copier.conf create mode 100644 tools/topology/topology2/include/components/igo_nr.conf create mode 100644 tools/topology/topology2/include/components/kpb.conf create mode 100644 tools/topology/topology2/include/components/level_multiplier.conf create mode 100644 tools/topology/topology2/include/components/level_multiplier/gain_-10_db.conf create mode 100644 tools/topology/topology2/include/components/level_multiplier/gain_-20_db.conf create mode 100644 tools/topology/topology2/include/components/level_multiplier/gain_-30_db.conf create mode 100644 tools/topology/topology2/include/components/level_multiplier/gain_-40_db.conf create mode 100644 tools/topology/topology2/include/components/level_multiplier/gain_0_db.conf create mode 100644 tools/topology/topology2/include/components/level_multiplier/gain_10_db.conf create mode 100644 tools/topology/topology2/include/components/level_multiplier/gain_20_db.conf create mode 100644 tools/topology/topology2/include/components/level_multiplier/gain_30_db.conf create mode 100644 tools/topology/topology2/include/components/level_multiplier/gain_40_db.conf create mode 100644 tools/topology/topology2/include/components/mfcc.conf create mode 100644 tools/topology/topology2/include/components/mfcc/default.conf create mode 100644 tools/topology/topology2/include/components/micsel.conf create mode 100644 tools/topology/topology2/include/components/micsel/downmix_51_to_mono.conf create mode 100644 tools/topology/topology2/include/components/micsel/downmix_51_to_mono_with_lfe.conf create mode 100644 tools/topology/topology2/include/components/micsel/downmix_51_to_stereo.conf create mode 100644 tools/topology/topology2/include/components/micsel/downmix_51_to_stereo_with_lfe.conf create mode 100644 tools/topology/topology2/include/components/micsel/downmix_71_to_51.conf create mode 100644 tools/topology/topology2/include/components/micsel/downmix_71_to_mono.conf create mode 100644 tools/topology/topology2/include/components/micsel/downmix_71_to_mono_with_lfe.conf create mode 100644 tools/topology/topology2/include/components/micsel/downmix_71_to_stereo.conf create mode 100644 tools/topology/topology2/include/components/micsel/downmix_71_to_stereo_with_lfe.conf create mode 100644 tools/topology/topology2/include/components/micsel/downmix_stereo_to_mono.conf create mode 100644 tools/topology/topology2/include/components/micsel/passthrough.conf create mode 100644 tools/topology/topology2/include/components/micsel/upmix_mono_to_51.conf create mode 100644 tools/topology/topology2/include/components/micsel/upmix_mono_to_71.conf create mode 100644 tools/topology/topology2/include/components/micsel/upmix_mono_to_stereo.conf create mode 100644 tools/topology/topology2/include/components/micsel/upmix_stereo_to_51.conf create mode 100644 tools/topology/topology2/include/components/micsel/upmix_stereo_to_71.conf create mode 100644 tools/topology/topology2/include/components/module-copier.conf create mode 100644 tools/topology/topology2/include/components/multiband_drc.conf create mode 100644 tools/topology/topology2/include/components/multiband_drc/default.conf create mode 100644 tools/topology/topology2/include/components/multiband_drc/passthrough.conf create mode 100644 tools/topology/topology2/include/components/ov_noise_suppression.conf create mode 100644 tools/topology/topology2/include/components/rtnr.conf create mode 100644 tools/topology/topology2/include/components/rtnr/default_bytes.conf create mode 100644 tools/topology/topology2/include/components/rtnr/default_data.conf create mode 100644 tools/topology/topology2/include/components/smart_amp.conf create mode 100644 tools/topology/topology2/include/components/sound_dose.conf create mode 100644 tools/topology/topology2/include/components/sound_dose/setup_data_init.conf create mode 100644 tools/topology/topology2/include/components/sound_dose/setup_gain_0db.conf create mode 100644 tools/topology/topology2/include/components/sound_dose/setup_sens_0db.conf create mode 100644 tools/topology/topology2/include/components/sound_dose/setup_sens_100db.conf create mode 100644 tools/topology/topology2/include/components/sound_dose/setup_vol_0db.conf create mode 100644 tools/topology/topology2/include/components/src_format_s16_convert_from_48k.conf create mode 100644 tools/topology/topology2/include/components/src_format_s16_convert_to_48k.conf create mode 100644 tools/topology/topology2/include/components/src_format_s16_passthrough.conf create mode 100644 tools/topology/topology2/include/components/src_format_s16_to_sxx_convert.conf create mode 100644 tools/topology/topology2/include/components/src_format_s24_convert_from_48k.conf create mode 100644 tools/topology/topology2/include/components/src_format_s24_convert_to_48k.conf create mode 100644 tools/topology/topology2/include/components/src_format_s24_passthrough.conf create mode 100644 tools/topology/topology2/include/components/src_format_s24_to_sxx_convert.conf create mode 100644 tools/topology/topology2/include/components/src_format_s32_convert_from_48k.conf create mode 100644 tools/topology/topology2/include/components/src_format_s32_convert_to_48k.conf create mode 100644 tools/topology/topology2/include/components/src_format_s32_passthrough.conf create mode 100644 tools/topology/topology2/include/components/src_format_s32_to_sxx_convert.conf create mode 100644 tools/topology/topology2/include/components/src_format_sxx_to_s16_convert.conf create mode 100644 tools/topology/topology2/include/components/src_format_sxx_to_s24_convert.conf create mode 100644 tools/topology/topology2/include/components/src_format_sxx_to_s32_convert.conf create mode 100644 tools/topology/topology2/include/components/src_lite.conf create mode 100644 tools/topology/topology2/include/components/tdfb.conf create mode 100644 tools/topology/topology2/include/components/tdfb/line2_50mm_azm90_90_13_16khz.conf create mode 100644 tools/topology/topology2/include/components/tdfb/line2_50mm_azm90_90_13_48khz.conf create mode 100644 tools/topology/topology2/include/components/tdfb/line2_50mm_pm0_30_90deg_16khz.conf create mode 100644 tools/topology/topology2/include/components/tdfb/line2_50mm_pm0_30_90deg_48khz.conf create mode 100644 tools/topology/topology2/include/components/tdfb/line2_50mm_pm90deg_16khz.conf create mode 100644 tools/topology/topology2/include/components/tdfb/line2_50mm_pm90deg_48khz.conf create mode 100644 tools/topology/topology2/include/components/tdfb/line2_generic_pm10deg_16khz.conf create mode 100644 tools/topology/topology2/include/components/tdfb/line2_generic_pm10deg_48khz.conf create mode 100644 tools/topology/topology2/include/components/tdfb/line2_pass.conf create mode 100644 tools/topology/topology2/include/components/tdfb/line4_28mm_azm90_90_13_16khz.conf create mode 100644 tools/topology/topology2/include/components/tdfb/line4_28mm_azm90_90_13_48khz.conf create mode 100644 tools/topology/topology2/include/components/tdfb/line4_28mm_pm90deg_16khz.conf create mode 100644 tools/topology/topology2/include/components/tdfb/line4_28mm_pm90deg_48khz.conf create mode 100644 tools/topology/topology2/include/components/tdfb/line4_generic_pm10deg_16khz.conf create mode 100644 tools/topology/topology2/include/components/tdfb/line4_generic_pm10deg_48khz.conf create mode 100644 tools/topology/topology2/include/components/tdfb/line4_pass.conf create mode 100644 tools/topology/topology2/include/components/tdfb/line4to2_pass.conf create mode 100644 tools/topology/topology2/include/components/template_comp.conf create mode 100644 tools/topology/topology2/include/components/wov.conf create mode 100644 tools/topology/topology2/include/controls/enum.conf create mode 100644 tools/topology/topology2/include/dais/hw_config_simple.conf create mode 100644 tools/topology/topology2/include/dais/sai.conf create mode 100644 tools/topology/topology2/include/pipelines/cavs/dai-copier-be.conf create mode 100644 tools/topology/topology2/include/pipelines/cavs/dai-copier-eqiir-module-copier-capture.conf create mode 100644 tools/topology/topology2/include/pipelines/cavs/dai-copier-gain-eqiir-module-copier-capture.conf create mode 100644 tools/topology/topology2/include/pipelines/cavs/dai-copier-gain-mixin-capture.conf create mode 100644 tools/topology/topology2/include/pipelines/cavs/dai-copier-gain-module-copier-capture.conf create mode 100644 tools/topology/topology2/include/pipelines/cavs/dai-kpb-be.conf create mode 100644 tools/topology/topology2/include/pipelines/cavs/deepbuffer-playback.conf create mode 100644 tools/topology/topology2/include/pipelines/cavs/gain-copier-capture.conf create mode 100644 tools/topology/topology2/include/pipelines/cavs/gain-module-copier.conf create mode 100644 tools/topology/topology2/include/pipelines/cavs/google-rtc-aec-capture.conf create mode 100644 tools/topology/topology2/include/pipelines/cavs/highpass-capture-be.conf create mode 100644 tools/topology/topology2/include/pipelines/cavs/host-copier-gain-mixin-playback.conf create mode 100644 tools/topology/topology2/include/pipelines/cavs/host-copier-gain-src-mixin-playback.conf create mode 100644 tools/topology/topology2/include/pipelines/cavs/host-copier-micsel-gain-mixin-playback.conf create mode 100644 tools/topology/topology2/include/pipelines/cavs/host-gateway-capture.conf create mode 100644 tools/topology/topology2/include/pipelines/cavs/host-gateway-playback.conf create mode 100644 tools/topology/topology2/include/pipelines/cavs/host-gateway-tdfb-drc-capture.conf create mode 100644 tools/topology/topology2/include/pipelines/cavs/io-gateway-capture.conf create mode 100644 tools/topology/topology2/include/pipelines/cavs/io-gateway.conf create mode 100644 tools/topology/topology2/include/pipelines/cavs/mixout-aria-gain-mixin-playback.conf create mode 100644 tools/topology/topology2/include/pipelines/cavs/mixout-dai-copier-playback.conf create mode 100644 tools/topology/topology2/include/pipelines/cavs/mixout-gain-alh-dai-copier-playback.conf create mode 100644 tools/topology/topology2/include/pipelines/cavs/mixout-gain-ctc-dai-copier-playback.conf create mode 100644 tools/topology/topology2/include/pipelines/cavs/mixout-gain-dai-copier-playback.conf create mode 100644 tools/topology/topology2/include/pipelines/cavs/mixout-gain-efx-dai-copier-playback.conf create mode 100644 tools/topology/topology2/include/pipelines/cavs/mixout-gain-efx-mbdrc-dai-copier-playback.conf create mode 100644 tools/topology/topology2/include/pipelines/cavs/mixout-gain-eqiir-dts-dai-copier-playback.conf create mode 100644 tools/topology/topology2/include/pipelines/cavs/mixout-gain-eqiir-eqfir-drc-alh-dai-copier-playback.conf create mode 100644 tools/topology/topology2/include/pipelines/cavs/mixout-gain-eqiir-eqfir-playback.conf create mode 100644 tools/topology/topology2/include/pipelines/cavs/mixout-gain-host-copier-capture.conf create mode 100644 tools/topology/topology2/include/pipelines/cavs/mixout-gain-smart-amp-dai-copier-playback.conf create mode 100644 tools/topology/topology2/include/pipelines/cavs/mixout-mixin.conf create mode 100644 tools/topology/topology2/include/pipelines/cavs/src-gain-mixin-playback.conf create mode 100644 tools/topology/topology2/include/pipelines/cavs/wov-detect.conf create mode 100644 tools/topology/topology2/include/pipelines/gain-capture.conf create mode 100644 tools/topology/topology2/include/pipelines/gain-playback.conf create mode 100644 tools/topology/topology2/platform/intel/bt-default.conf create mode 100644 tools/topology/topology2/platform/intel/bt-generic.conf create mode 100644 tools/topology/topology2/platform/intel/bt-ssp-config-lbm.conf create mode 100644 tools/topology/topology2/platform/intel/bt-ssp-config.conf create mode 100644 tools/topology/topology2/platform/intel/deep-buffer-default.conf create mode 100644 tools/topology/topology2/platform/intel/deep-buffer.conf create mode 100644 tools/topology/topology2/platform/intel/dmic-default.conf create mode 100644 tools/topology/topology2/platform/intel/dmic-generic.conf create mode 100644 tools/topology/topology2/platform/intel/dmic-wov.conf create mode 100644 tools/topology/topology2/platform/intel/dmic1-mfcc.conf create mode 100644 tools/topology/topology2/platform/intel/dmic1-passthrough.conf create mode 100644 tools/topology/topology2/platform/intel/efx-default.conf create mode 100644 tools/topology/topology2/platform/intel/google-rtc-aec-reference.conf create mode 100644 tools/topology/topology2/platform/intel/hdmi-default.conf create mode 100644 tools/topology/topology2/platform/intel/hdmi-generic.conf create mode 100644 tools/topology/topology2/platform/intel/hdmi-in-capture.conf create mode 100644 tools/topology/topology2/platform/intel/hdmi-in-default.conf create mode 100644 tools/topology/topology2/platform/intel/hdmi-in-ssp-config.conf create mode 100644 tools/topology/topology2/platform/intel/hw_config_cardinal_clk.conf create mode 100644 tools/topology/topology2/platform/intel/lnl.conf create mode 100644 tools/topology/topology2/platform/intel/mtl.conf create mode 100644 tools/topology/topology2/platform/intel/nocodec-ssp0-2level.conf create mode 100644 tools/topology/topology2/platform/intel/ptl.conf create mode 100644 tools/topology/topology2/platform/intel/sdw-amp-generic.conf create mode 100644 tools/topology/topology2/platform/intel/sdw-dmic-generic.conf create mode 100644 tools/topology/topology2/platform/intel/sdw-jack-generic.conf create mode 100644 tools/topology/topology2/platform/intel/speaker-echo-ref.conf create mode 100644 tools/topology/topology2/platform/intel/ssp-default.conf create mode 100644 tools/topology/topology2/platform/intel/ssp_aux_config.conf create mode 100644 tools/topology/topology2/platform/intel/tgl.conf create mode 100644 tools/topology/topology2/production/CMakeLists.txt create mode 100644 tools/topology/topology2/production/tplg-targets-ace1.cmake create mode 100644 tools/topology/topology2/production/tplg-targets-ace2.cmake create mode 100644 tools/topology/topology2/production/tplg-targets-ace3.cmake create mode 100644 tools/topology/topology2/production/tplg-targets-cavs25.cmake create mode 100644 tools/topology/topology2/production/tplg-targets-hda-generic.cmake create mode 100644 tools/topology/topology2/production/tplg-targets-imx8.cmake create mode 100644 tools/topology/topology2/production/tplg-targets-sdca-generic.cmake create mode 100644 tools/topology/topology2/sof-hda-generic.conf create mode 100644 tools/tplg_parser/asrc.c create mode 100644 tools/tplg_parser/audio_formats.c create mode 100644 tools/tplg_parser/buffer.c create mode 100644 tools/tplg_parser/graph.c create mode 100644 tools/tplg_parser/host.c create mode 100644 tools/tplg_parser/include/linux/types.h create mode 100644 tools/tplg_parser/include/tplg_parser/tokens.h create mode 100644 tools/tplg_parser/object.c create mode 100644 tools/tplg_parser/src.c create mode 100644 tools/tune/common/sof_alsactl_write.m create mode 100644 tools/tune/common/sof_blob_write.m create mode 100644 tools/tune/common/sof_check_blob_header.m create mode 100644 tools/tune/common/sof_check_create_dir.m create mode 100644 tools/tune/common/sof_get_abi.m create mode 100644 tools/tune/common/sof_tplg2_write.m create mode 100644 tools/tune/common/sof_tplg_write.m create mode 100644 tools/tune/common/sof_ucm_blob_write.m create mode 100644 tools/tune/dmic/dmic_fir_export_alsa.m create mode 100644 tools/tune/mfcc/README.txt create mode 100644 tools/tune/mfcc/decode_ceps.m create mode 100644 tools/tune/mfcc/run_mfcc.sh create mode 100644 tools/tune/mfcc/setup_mfcc.m mode change 100755 => 100644 tools/tune/tdfb/example_all.sh diff --git a/scripts/build-tools.sh b/scripts/build-tools.sh index 496ca4c2d7cc..6304265820cd 100755 --- a/scripts/build-tools.sh +++ b/scripts/build-tools.sh @@ -10,24 +10,28 @@ SOF_TOP=$(cd "$(dirname "$0")/.." && pwd) print_usage() { cat < generates"); - fprintf(stdout, " the current ABI header with given payload size\n"); fprintf(stdout, " -c control name e.g."); fprintf(stdout, " numid=22,name=\\\"EQIIR1.0 EQIIR\\\"\"\n"); fprintf(stdout, " -n control id e.g. 22\n"); - fprintf(stdout, " -s set data using ASCII CSV input file\n"); + fprintf(stdout, " -i {3|4} selects the IPC type to use, defaults to 3 (IPC3)\n"); + fprintf(stdout, " -g generates"); + fprintf(stdout, " the current ABI header with given payload size\n"); + fprintf(stdout, " -s set data, default is using ASCII CSV input file\n"); fprintf(stdout, " -b set/get control in binary mode(e.g. for set, use binary input file, for get, dump out in hex format)\n"); fprintf(stdout, " -r no abi header for the input file, or not dumping abi header for get.\n"); fprintf(stdout, " -o specify the output file.\n"); - fprintf(stdout, " -t specify the component specified type.\n"); + fprintf(stdout, " -t specify the component specified type (IPC3 specific).\n"); + fprintf(stdout, " -p specify the param_id of the data (IPC4 specific)."); + fprintf(stdout, " Valid range: 0-255\n"); } static void header_init(struct ctl_data *ctl_data) @@ -85,9 +102,10 @@ static void header_init(struct ctl_data *ctl_data) struct sof_abi_hdr *hdr = (struct sof_abi_hdr *)&ctl_data->buffer[BUFFER_ABI_OFFSET]; - hdr->magic = SOF_ABI_MAGIC; + hdr->magic = ctl_data->magic; hdr->type = ctl_data->type; hdr->abi = SOF_ABI_VERSION; + ctl_data->buffer[BUFFER_TAG_OFFSET] = SOF_CTRL_CMD_BINARY; } /* Returns the number of bytes written to the control buffer */ @@ -102,7 +120,7 @@ static int read_setup(struct ctl_data *ctl_data) int separator; int n = 0; FILE *fh; - int data_start_int_index = BUFFER_ABI_OFFSET; + int data_start_int_index = 0; int data_int_index; /* open input file */ @@ -116,7 +134,7 @@ static int read_setup(struct ctl_data *ctl_data) if (ctl_data->no_abi) { header_init(ctl_data); abi_size = sizeof(struct sof_abi_hdr); - data_start_int_index += abi_size / sizeof(uint32_t); + data_start_int_index += (abi_size + BUFFER_TLV_HEADER_BYTES) / sizeof(uint32_t); } if (ctl_data->binary) { @@ -148,6 +166,8 @@ static int read_setup(struct ctl_data *ctl_data) if (ctl_data->no_abi) { hdr->size = n; n += abi_size; + ctl_data->buffer[BUFFER_SIZE_OFFSET] = n; + n += BUFFER_TLV_HEADER_BYTES; } if (n > n_max) { @@ -156,6 +176,11 @@ static int read_setup(struct ctl_data *ctl_data) fprintf(stderr, "Please check the data file.\n"); } + if (hdr->magic != ctl_data->magic) + fprintf(stderr, + "Info: ABI mismatch: expecting: 0x%8.8x, got: 0x%8.8x\n", + ctl_data->magic, hdr->magic); + fclose(fh); return n; } @@ -166,7 +191,10 @@ static void header_dump(struct ctl_data *ctl_data) (struct sof_abi_hdr *)&ctl_data->buffer[BUFFER_ABI_OFFSET]; fprintf(stdout, "hdr: magic 0x%8.8x\n", hdr->magic); - fprintf(stdout, "hdr: type %d\n", hdr->type); + if (ctl_data->ipc_type == SOF_IPC_TYPE_3) + fprintf(stdout, "hdr: type %u\n", hdr->type); + else + fprintf(stdout, "hdr: param_id %u\n", hdr->type); fprintf(stdout, "hdr: size %d bytes\n", hdr->size); fprintf(stdout, "hdr: abi %d:%d:%d\n", SOF_ABI_VERSION_MAJOR(hdr->abi), @@ -183,16 +211,16 @@ static void hex_data_dump(struct ctl_data *ctl_data) int i; /* calculate the dumping units */ - n = ctl_data->buffer[BUFFER_SIZE_OFFSET] / sizeof(uint16_t); + n = (ctl_data->buffer[BUFFER_SIZE_OFFSET] + BUFFER_TLV_HEADER_BYTES) / sizeof(uint16_t); /* exclude the type and size header */ - int_offset = 2; + int_offset = 0; /* exclude abi header if '-r' specified */ if (ctl_data->no_abi) { - int_offset += sizeof(struct sof_abi_hdr) / + int_offset += (sizeof(struct sof_abi_hdr) + BUFFER_TLV_HEADER_BYTES) / sizeof(uint32_t); - n -= sizeof(struct sof_abi_hdr) / + n -= (sizeof(struct sof_abi_hdr) + BUFFER_TLV_HEADER_BYTES) / sizeof(uint16_t); } @@ -219,11 +247,11 @@ static void csv_data_dump(struct ctl_data *ctl_data, FILE *fh) int i; int s = 0; - config = &ctl_data->buffer[BUFFER_ABI_OFFSET]; - n = ctl_data->buffer[BUFFER_SIZE_OFFSET] / sizeof(uint32_t); + config = &ctl_data->buffer[0]; + n = (ctl_data->buffer[BUFFER_SIZE_OFFSET] + BUFFER_TLV_HEADER_BYTES) / sizeof(uint32_t); if (ctl_data->no_abi) - s = sizeof(struct sof_abi_hdr) / sizeof(uint32_t); + s = (sizeof(struct sof_abi_hdr) + BUFFER_TLV_HEADER_BYTES) / sizeof(uint32_t); /* Print out in CSV txt formal */ for (i = s; i < n; i++) { @@ -275,10 +303,7 @@ static int buffer_alloc(struct ctl_data *ctl_data) return -EINVAL; } - ctl_data->buffer[BUFFER_TAG_OFFSET] = SOF_CTRL_CMD_BINARY; - ctl_data->buffer_size = buffer_size; - return 0; } @@ -406,7 +431,7 @@ static int ctl_free(struct ctl_data *ctl_data) return ret; } -static void ctl_dump(struct ctl_data *ctl_data) +static void ctl_dump(struct ctl_data *ctl_data, size_t dump_size) { FILE *fh; int offset = 0; @@ -422,13 +447,16 @@ static void ctl_dump(struct ctl_data *ctl_data) return; } - offset = BUFFER_ABI_OFFSET; - n = ctl_data->buffer[BUFFER_SIZE_OFFSET]; + if (dump_size) + n = dump_size; + else + n = ctl_data->buffer[BUFFER_SIZE_OFFSET] + BUFFER_TLV_HEADER_BYTES; if (ctl_data->no_abi) { - offset += sizeof(struct sof_abi_hdr) / - sizeof(int); + offset = sizeof(struct sof_abi_hdr) / sizeof(int) + + BUFFER_ABI_OFFSET; n -= sizeof(struct sof_abi_hdr); + n -= BUFFER_ABI_OFFSET * sizeof(uint32_t); } n = fwrite(&ctl_data->buffer[offset], 1, n, fh); @@ -453,7 +481,7 @@ static void ctl_dump(struct ctl_data *ctl_data) static int ctl_set_get(struct ctl_data *ctl_data) { int ret; - size_t n; + size_t read_size, ref_size; if (!ctl_data->buffer) { fprintf(stderr, "Error: No buffer for set/get!\n"); @@ -465,14 +493,22 @@ static int ctl_set_get(struct ctl_data *ctl_data) ctl_data->input_file); fprintf(stdout, "into device %s control %s.\n", ctl_data->dev, ctl_data->cname); - n = read_setup(ctl_data); - if (n < 1) { + read_size = read_setup(ctl_data); + if (read_size < 1) { fprintf(stderr, "Error: failed data read from %s.\n", ctl_data->input_file); return -EINVAL; } - ctl_data->buffer[BUFFER_SIZE_OFFSET] = n; + ref_size = ctl_data->buffer[BUFFER_SIZE_OFFSET] + BUFFER_TLV_HEADER_BYTES; + if (read_size != ref_size) { + fprintf(stderr, + "Error: Blob TLV header size %u (plus %lu) does not match with read bytes count %zu.\n", + ctl_data->buffer[BUFFER_SIZE_OFFSET], BUFFER_TLV_HEADER_BYTES, + read_size); + return -EINVAL; + } + ret = snd_ctl_elem_tlv_write(ctl_data->ctl, ctl_data->id, ctl_data->buffer); if (ret < 0) { @@ -485,6 +521,8 @@ static int ctl_set_get(struct ctl_data *ctl_data) fprintf(stdout, "Retrieving configuration for "); fprintf(stdout, "device %s control %s.\n", ctl_data->dev, ctl_data->cname); + /* set the ABI header to pass the param ID */ + header_init(ctl_data); ctl_data->buffer[BUFFER_SIZE_OFFSET] = ctl_data->ctrl_size; ret = snd_ctl_elem_tlv_read(ctl_data->ctl, ctl_data->id, ctl_data->buffer, @@ -504,6 +542,7 @@ int main(int argc, char *argv[]) char *input_file = NULL; char *output_file = NULL; struct ctl_data *ctl_data; + int ipc_type = 3; char nname[256]; int ret = 0; int opt; @@ -518,7 +557,7 @@ int main(int argc, char *argv[]) ctl_data->dev = "hw:0"; - while ((opt = getopt(argc, argv, "hD:c:s:n:o:t:g:br")) != -1) { + while ((opt = getopt(argc, argv, "hD:c:s:n:i:o:t:p:g:br")) != -1) { switch (opt) { case 'D': ctl_data->dev = optarg; @@ -530,6 +569,9 @@ int main(int argc, char *argv[]) sprintf(nname, "numid=%d", atoi(optarg)); ctl_data->cname = nname; break; + case 'i': + ipc_type = atoi(optarg); + break; case 's': input_file = optarg; ctl_data->input_file = input_file; @@ -545,6 +587,7 @@ int main(int argc, char *argv[]) ctl_data->no_abi = true; break; case 't': + case 'p': ctl_data->type = atoi(optarg); break; case 'g': @@ -559,6 +602,35 @@ int main(int argc, char *argv[]) } } + /* All arguments are switches, error if something still remains in command line */ + if (optind < argc) { + fprintf(stderr, "Error: Non-supported argument %s.\n", + argv[optind]); + return -EINVAL; + } + + switch (ipc_type) { + case 3: + ctl_data->ipc_type = SOF_IPC_TYPE_3; + ctl_data->magic = SOF_ABI_MAGIC; + break; + case 4: + if (ctl_data->type > 255) { + fprintf(stderr, "error: The param_id %u is out of range\n\n", + ctl_data->type); + usage(argv[0]); + goto struct_free; + } + + ctl_data->ipc_type = SOF_IPC_TYPE_4; + ctl_data->magic = SOF_IPC4_ABI_MAGIC; + break; + default: + fprintf(stderr, "error: Unsupported IPC type: %u\n\n", ipc_type); + usage(argv[0]); + goto struct_free; + } + /* open output file */ if (output_file) { ctl_data->out_fd = open(output_file, O_CREAT | O_TRUNC | O_RDWR, @@ -577,8 +649,8 @@ int main(int argc, char *argv[]) hdr = (struct sof_abi_hdr *) &ctl_data->buffer[BUFFER_ABI_OFFSET]; hdr->size = ctl_data->print_abi_size; - ctl_data->buffer[BUFFER_SIZE_OFFSET] = ctl_data->ctrl_size; - ctl_dump(ctl_data); + ctl_data->buffer[BUFFER_SIZE_OFFSET] = ctl_data->ctrl_size + hdr->size; + ctl_dump(ctl_data, sizeof(struct sof_abi_hdr) + BUFFER_TLV_HEADER_BYTES); buffer_free(ctl_data); goto out_fd_close; } @@ -616,7 +688,7 @@ int main(int argc, char *argv[]) } /* dump the tlv buffer to a file or stdout */ - ctl_dump(ctl_data); + ctl_dump(ctl_data, 0); data_free: ret = ctl_free(ctl_data); diff --git a/tools/ctl/ipc3/eq_fir/flat.txt b/tools/ctl/ipc3/eq_fir/flat.txt new file mode 100644 index 000000000000..ed8f327e17ee --- /dev/null +++ b/tools/ctl/ipc3/eq_fir/flat.txt @@ -0,0 +1 @@ +3,88,4607827,0,56,50450433,0,0,0,0,56,65538,0,0,0,0,0,4294901764,0,0,0,0,16384,0 diff --git a/tools/ctl/ipc3/eq_fir/loudness.txt b/tools/ctl/ipc3/eq_fir/loudness.txt new file mode 100644 index 000000000000..40cc0f19924e --- /dev/null +++ b/tools/ctl/ipc3/eq_fir/loudness.txt @@ -0,0 +1 @@ +3,616,4607827,0,584,50450433,0,0,0,0,584,131076,0,0,0,0,65537,65537,4294901764,0,0,0,0,16384,0,252,0,0,0,0,65537,65537,65537,65537,131073,131074,131074,196611,196611,262147,262148,327685,393221,393222,458759,524296,589833,655370,720907,786444,851981,983054,1048591,1179665,1245202,1376276,1507350,1638424,1769498,1966109,2162719,2359330,2621478,2818089,3080237,3407922,3670070,3997755,4325440,4718661,5046347,5505105,5963863,6422622,6815845,7274604,7733362,8192121,8781953,9568396,10420376,11272357,12058802,12976318,14155982,15335649,16056559,16515324,16711960,24117551,70844611,3920495315,3920522431,70845139,24117443,16711983,16515352,16056572,15335663,14156001,12976334,12058814,11272370,10420389,9568408,8781964,8192129,7733369,7274610,6815852,6422629,5963870,5505111,5046353,4718667,4325445,3997760,3670075,3407926,3080242,2818093,2621481,2359334,2162722,1966111,1769501,1638426,1507352,1376278,1245204,1179666,1048593,983055,851982,786445,720908,655371,589834,524297,458760,393223,393222,327685,262149,262148,196611,196611,131075,131074,131074,65537,65537,65537,65537,1,0 diff --git a/tools/ctl/ipc3/eq_fir/mid.txt b/tools/ctl/ipc3/eq_fir/mid.txt new file mode 100644 index 000000000000..3d1f8b502d51 --- /dev/null +++ b/tools/ctl/ipc3/eq_fir/mid.txt @@ -0,0 +1 @@ +3,160,4607827,0,128,50450433,0,0,0,0,128,65538,0,0,0,0,0,65576,0,0,0,0,1202154746,4222755303,3873958283,4024953600,4126012299,4176934804,4217764409,4250664050,4273864222,4288806716,2490349,6750285,8126582,7733371,6488174,4980824,3473472,2293804,1245211,1245197 diff --git a/tools/ctl/ipc3/eq_fir/pass.txt b/tools/ctl/ipc3/eq_fir/pass.txt new file mode 100644 index 000000000000..501ef2821697 --- /dev/null +++ b/tools/ctl/ipc3/eq_fir/pass.txt @@ -0,0 +1 @@ +3,88,4607827,0,56,50450433,0,0,0,0,56,65538,0,0,0,0,4294967295,4294901764,0,0,0,0,16384,0 diff --git a/tools/ctl/ipc3/eq_iir/bandpass.txt b/tools/ctl/ipc3/eq_iir/bandpass.txt new file mode 100644 index 000000000000..214dc382fd85 --- /dev/null +++ b/tools/ctl/ipc3/eq_iir/bandpass.txt @@ -0,0 +1 @@ +3,148,4607827,0,116,50450433,0,0,0,0,116,2,1,0,0,0,0,0,0,2,2,0,0,0,0,3316150158,2048164275,513807534,3267352229,513807534,0,16384,3867454526,1191025347,38870735,77741469,38870735,4294967292,24197 diff --git a/tools/ctl/ipc3/eq_iir/bassboost.txt b/tools/ctl/ipc3/eq_iir/bassboost.txt new file mode 100644 index 000000000000..82e849f5603e --- /dev/null +++ b/tools/ctl/ipc3/eq_iir/bassboost.txt @@ -0,0 +1 @@ +3,148,4607827,0,116,50450433,0,0,0,0,116,2,1,0,0,0,0,0,0,2,2,0,0,0,0,3227172081,2141520527,536653443,3221660410,536653443,0,16384,3260252783,2107733822,161646111,3961037800,172645501,4294967294,27910 diff --git a/tools/ctl/ipc3/eq_iir/bundle.txt b/tools/ctl/ipc3/eq_iir/bundle.txt new file mode 100644 index 000000000000..ef8a861b777f --- /dev/null +++ b/tools/ctl/ipc3/eq_iir/bundle.txt @@ -0,0 +1 @@ +3,440,4607827,0,408,50450433,0,0,0,0,408,2,5,0,0,0,0,2,2,1,1,0,0,0,0,0,0,0,0,538145694,0,32690,3,3,0,0,0,0,3237960220,2130690484,297056159,3694463533,303476880,0,16384,3302357025,2064935920,245217319,3776455865,274003881,0,16384,4106268671,4130185751,69182517,4179658584,365641401,4294967292,25468,2,2,0,0,0,0,3227172081,2141520527,536653443,3221660410,536653443,0,16384,3260252783,2107733822,161646111,3961037800,172645501,4294967294,27910,2,2,0,0,0,0,3316150158,2048164275,513807534,3267352229,513807534,0,16384,3867454526,1191025347,38870735,77741469,38870735,4294967292,24197,1,1,0,0,0,0,3240919741,2127607086,533187596,3228592105,533187596,4294967292,20433 diff --git a/tools/ctl/ipc3/eq_iir/flat.txt b/tools/ctl/ipc3/eq_iir/flat.txt new file mode 100644 index 000000000000..25fc6796f600 --- /dev/null +++ b/tools/ctl/ipc3/eq_iir/flat.txt @@ -0,0 +1 @@ +3,120,4607827,0,88,50450433,0,0,0,0,88,2,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,538145694,0,32690 diff --git a/tools/ctl/ipc3/eq_iir/highpass_20hz_0db_48khz.txt b/tools/ctl/ipc3/eq_iir/highpass_20hz_0db_48khz.txt new file mode 100644 index 000000000000..e642a5fc82b2 --- /dev/null +++ b/tools/ctl/ipc3/eq_iir/highpass_20hz_0db_48khz.txt @@ -0,0 +1 @@ +3,120,4607827,0,88,50450433,0,0,0,0,88,2,1,0,0,0,0,0,0,1,1,0,0,0,0,3225193546,2143508228,537150400,3220666496,537150400,0,32690 diff --git a/tools/ctl/ipc3/eq_iir/highpass_30hz_0db_48khz.txt b/tools/ctl/ipc3/eq_iir/highpass_30hz_0db_48khz.txt new file mode 100644 index 000000000000..4bc0d715fd96 --- /dev/null +++ b/tools/ctl/ipc3/eq_iir/highpass_30hz_0db_48khz.txt @@ -0,0 +1 @@ +3,120,4607827,0,88,50450433,0,0,0,0,88,2,1,0,0,0,0,0,0,1,1,0,0,0,0,3227172081,2141520527,536653443,3221660410,536653443,0,32690 diff --git a/tools/ctl/ipc3/eq_iir/highpass_40hz_0db_48khz.txt b/tools/ctl/ipc3/eq_iir/highpass_40hz_0db_48khz.txt new file mode 100644 index 000000000000..b132779d1ace --- /dev/null +++ b/tools/ctl/ipc3/eq_iir/highpass_40hz_0db_48khz.txt @@ -0,0 +1 @@ +3,120,4607827,0,88,50450433,0,0,0,0,88,2,1,0,0,0,0,0,0,1,1,0,0,0,0,3229146956,2139532835,536156946,3222653403,536156946,0,32690 diff --git a/tools/ctl/ipc3/eq_iir/highpass_50hz_0db_48khz.txt b/tools/ctl/ipc3/eq_iir/highpass_50hz_0db_48khz.txt new file mode 100644 index 000000000000..c25060036801 --- /dev/null +++ b/tools/ctl/ipc3/eq_iir/highpass_50hz_0db_48khz.txt @@ -0,0 +1 @@ +3,120,4607827,0,88,50450433,0,0,0,0,88,2,1,0,0,0,0,0,0,1,1,0,0,0,0,3231118179,2137545158,535660909,3223645479,535660909,0,32690 diff --git a/tools/ctl/ipc3/eq_iir/loudness.txt b/tools/ctl/ipc3/eq_iir/loudness.txt new file mode 100644 index 000000000000..1cf6a66fa25a --- /dev/null +++ b/tools/ctl/ipc3/eq_iir/loudness.txt @@ -0,0 +1 @@ +3,236,4607827,0,204,50450433,0,0,0,0,204,4,2,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,538145694,0,32690,3,3,0,0,0,0,3237960220,2130690484,297056159,3694463533,303476880,0,16384,3302357025,2064935920,245217319,3776455865,274003881,0,16384,4106268671,4130185751,69182517,4179658584,365641401,4294967292,25468 diff --git a/tools/ctl/ipc3/eq_iir/pass.txt b/tools/ctl/ipc3/eq_iir/pass.txt new file mode 100644 index 000000000000..ce79acad80fd --- /dev/null +++ b/tools/ctl/ipc3/eq_iir/pass.txt @@ -0,0 +1 @@ +3,120,4607827,0,88,50450433,0,0,0,0,88,2,1,0,0,0,0,4294967295,4294967295,1,1,0,0,0,0,0,0,0,0,538145694,0,32690 diff --git a/tools/ctl/ipc4/drc/generic_notebook_speaker.txt b/tools/ctl/ipc4/drc/generic_notebook_speaker.txt new file mode 100644 index 000000000000..104210eec4d9 --- /dev/null +++ b/tools/ctl/ipc4/drc/generic_notebook_speaker.txt @@ -0,0 +1 @@ +877023059,0,108,50438144,0,0,0,0,108,0,0,0,0,1,3875536896,251658240,167772160,6442451,60380940,107374182,12708444,2327743,4292230659,5305422,162898102,73470028,7456540,4286019447,2062296,5,4423680,294359,2477728,622039,46513, diff --git a/tools/ctl/ipc4/drc/passthrough.txt b/tools/ctl/ipc4/drc/passthrough.txt new file mode 100644 index 000000000000..e650ab9a26e3 --- /dev/null +++ b/tools/ctl/ipc4/drc/passthrough.txt @@ -0,0 +1 @@ +3,140,877023059,0,108,50450433,0,0,0,0,108,0,0,0,0,0,3892314112,503316480,201326592,6442451,67748529,89478485,2030160,9723989,4285175934,33474947,575096676,24401431,7456540,4286019447,2062296,5,4423680,294359,2477728,622039,46513 diff --git a/tools/ctl/ipc4/eq_fir/flat.txt b/tools/ctl/ipc4/eq_fir/flat.txt new file mode 100644 index 000000000000..5fd026ca0c20 --- /dev/null +++ b/tools/ctl/ipc4/eq_fir/flat.txt @@ -0,0 +1 @@ +3,88,877023059,0,56,50450433,0,0,0,0,56,65538,0,0,0,0,0,4294901764,0,0,0,0,16384,0 diff --git a/tools/ctl/ipc4/eq_fir/loudness.txt b/tools/ctl/ipc4/eq_fir/loudness.txt new file mode 100644 index 000000000000..e5093f015b0e --- /dev/null +++ b/tools/ctl/ipc4/eq_fir/loudness.txt @@ -0,0 +1 @@ +3,616,877023059,0,584,50450433,0,0,0,0,584,131076,0,0,0,0,65537,65537,4294901764,0,0,0,0,16384,0,252,0,0,0,0,65537,65537,65537,65537,131073,131074,131074,196611,196611,262147,262148,327685,393221,393222,458759,524296,589833,655370,720907,786444,851981,983054,1048591,1179665,1245202,1376276,1507350,1638424,1769498,1966109,2162719,2359330,2621478,2818089,3080237,3407922,3670070,3997755,4325440,4718661,5046347,5505105,5963863,6422622,6815845,7274604,7733362,8192121,8781953,9568396,10420376,11272357,12058802,12976318,14155982,15335649,16056559,16515324,16711960,24117551,70844611,3920495315,3920522431,70845139,24117443,16711983,16515352,16056572,15335663,14156001,12976334,12058814,11272370,10420389,9568408,8781964,8192129,7733369,7274610,6815852,6422629,5963870,5505111,5046353,4718667,4325445,3997760,3670075,3407926,3080242,2818093,2621481,2359334,2162722,1966111,1769501,1638426,1507352,1376278,1245204,1179666,1048593,983055,851982,786445,720908,655371,589834,524297,458760,393223,393222,327685,262149,262148,196611,196611,131075,131074,131074,65537,65537,65537,65537,1,0 diff --git a/tools/ctl/ipc4/eq_fir/mid.txt b/tools/ctl/ipc4/eq_fir/mid.txt new file mode 100644 index 000000000000..36d74f693e46 --- /dev/null +++ b/tools/ctl/ipc4/eq_fir/mid.txt @@ -0,0 +1 @@ +3,160,877023059,0,128,50450433,0,0,0,0,128,65538,0,0,0,0,0,65576,0,0,0,0,1202154746,4222755303,3873958283,4024953600,4126012299,4176934804,4217764409,4250664050,4273864222,4288806716,2490349,6750285,8126582,7733371,6488174,4980824,3473472,2293804,1245211,1245197 diff --git a/tools/ctl/ipc4/eq_fir/pass.txt b/tools/ctl/ipc4/eq_fir/pass.txt new file mode 100644 index 000000000000..5decf09aed40 --- /dev/null +++ b/tools/ctl/ipc4/eq_fir/pass.txt @@ -0,0 +1 @@ +3,88,877023059,0,56,50450433,0,0,0,0,56,65538,0,0,0,0,4294967295,4294901764,0,0,0,0,16384,0 diff --git a/tools/ctl/ipc4/eq_iir/bandpass.txt b/tools/ctl/ipc4/eq_iir/bandpass.txt new file mode 100644 index 000000000000..3509ec8c7cf2 --- /dev/null +++ b/tools/ctl/ipc4/eq_iir/bandpass.txt @@ -0,0 +1 @@ +3,148,877023059,0,116,50450433,0,0,0,0,116,2,1,0,0,0,0,0,0,2,2,0,0,0,0,3316150158,2048164275,513807534,3267352229,513807534,0,16384,3867454526,1191025347,38870735,77741469,38870735,4294967292,24197 diff --git a/tools/ctl/ipc4/eq_iir/bassboost.txt b/tools/ctl/ipc4/eq_iir/bassboost.txt new file mode 100644 index 000000000000..8d96a8c7fc68 --- /dev/null +++ b/tools/ctl/ipc4/eq_iir/bassboost.txt @@ -0,0 +1 @@ +3,148,877023059,0,116,50450433,0,0,0,0,116,2,1,0,0,0,0,0,0,2,2,0,0,0,0,3227172081,2141520527,536653443,3221660410,536653443,0,16384,3260252783,2107733822,161646111,3961037800,172645501,4294967294,27910 diff --git a/tools/ctl/ipc4/eq_iir/flat.txt b/tools/ctl/ipc4/eq_iir/flat.txt new file mode 100644 index 000000000000..cb78a292eda8 --- /dev/null +++ b/tools/ctl/ipc4/eq_iir/flat.txt @@ -0,0 +1 @@ +3,120,877023059,0,88,50450433,0,0,0,0,88,2,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,538145694,0,32690 diff --git a/tools/ctl/ipc4/eq_iir/highpass_20hz_0db_48khz.txt b/tools/ctl/ipc4/eq_iir/highpass_20hz_0db_48khz.txt new file mode 100644 index 000000000000..6d2ced3f43a3 --- /dev/null +++ b/tools/ctl/ipc4/eq_iir/highpass_20hz_0db_48khz.txt @@ -0,0 +1 @@ +3,120,877023059,0,88,50450433,0,0,0,0,88,2,1,0,0,0,0,0,0,1,1,0,0,0,0,3225193546,2143508228,537150400,3220666496,537150400,0,32690 diff --git a/tools/ctl/ipc4/eq_iir/highpass_30hz_0db_48khz.txt b/tools/ctl/ipc4/eq_iir/highpass_30hz_0db_48khz.txt new file mode 100644 index 000000000000..a87e38ddcbfa --- /dev/null +++ b/tools/ctl/ipc4/eq_iir/highpass_30hz_0db_48khz.txt @@ -0,0 +1 @@ +3,120,877023059,0,88,50450433,0,0,0,0,88,2,1,0,0,0,0,0,0,1,1,0,0,0,0,3227172081,2141520527,536653443,3221660410,536653443,0,32690 diff --git a/tools/ctl/ipc4/eq_iir/highpass_40hz_0db_48khz.txt b/tools/ctl/ipc4/eq_iir/highpass_40hz_0db_48khz.txt new file mode 100644 index 000000000000..3aafc5060347 --- /dev/null +++ b/tools/ctl/ipc4/eq_iir/highpass_40hz_0db_48khz.txt @@ -0,0 +1 @@ +3,120,877023059,0,88,50450433,0,0,0,0,88,2,1,0,0,0,0,0,0,1,1,0,0,0,0,3229146956,2139532835,536156946,3222653403,536156946,0,32690 diff --git a/tools/ctl/ipc4/eq_iir/highpass_40hz_20db_48khz.txt b/tools/ctl/ipc4/eq_iir/highpass_40hz_20db_48khz.txt new file mode 100644 index 000000000000..4a4f1459a3ac --- /dev/null +++ b/tools/ctl/ipc4/eq_iir/highpass_40hz_20db_48khz.txt @@ -0,0 +1 @@ +3,120,877023059,0,88,50450433,0,0,0,0,88,2,1,0,0,0,0,0,0,1,1,0,0,0,0,3229146956,2139532835,536156946,3222653403,536156946,4294967292,20432 diff --git a/tools/ctl/ipc4/eq_iir/highpass_40hz_30db_48khz.txt b/tools/ctl/ipc4/eq_iir/highpass_40hz_30db_48khz.txt new file mode 100644 index 000000000000..d997e8f3131b --- /dev/null +++ b/tools/ctl/ipc4/eq_iir/highpass_40hz_30db_48khz.txt @@ -0,0 +1 @@ +3,120,877023059,0,88,50450433,0,0,0,0,88,2,1,0,0,0,0,0,0,1,1,0,0,0,0,3229146956,2139532835,536156946,3222653403,536156946,4294967291,32305 diff --git a/tools/ctl/ipc4/eq_iir/highpass_40hz_40db_48khz.txt b/tools/ctl/ipc4/eq_iir/highpass_40hz_40db_48khz.txt new file mode 100644 index 000000000000..082ed00b7e31 --- /dev/null +++ b/tools/ctl/ipc4/eq_iir/highpass_40hz_40db_48khz.txt @@ -0,0 +1 @@ +3,120,877023059,0,88,50450433,0,0,0,0,88,2,1,0,0,0,0,0,0,1,1,0,0,0,0,3229146956,2139532835,536156946,3222653403,536156946,4294967289,25539 diff --git a/tools/ctl/ipc4/eq_iir/highpass_50hz_0db_48khz.txt b/tools/ctl/ipc4/eq_iir/highpass_50hz_0db_48khz.txt new file mode 100644 index 000000000000..b7822d4dc230 --- /dev/null +++ b/tools/ctl/ipc4/eq_iir/highpass_50hz_0db_48khz.txt @@ -0,0 +1 @@ +3,120,877023059,0,88,50450433,0,0,0,0,88,2,1,0,0,0,0,0,0,1,1,0,0,0,0,3231118179,2137545158,535660909,3223645479,535660909,0,32690 diff --git a/tools/ctl/ipc4/eq_iir/loudness.txt b/tools/ctl/ipc4/eq_iir/loudness.txt new file mode 100644 index 000000000000..fed1b4f25578 --- /dev/null +++ b/tools/ctl/ipc4/eq_iir/loudness.txt @@ -0,0 +1 @@ +3,236,877023059,0,204,50450433,0,0,0,0,204,4,2,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,538145694,0,32690,3,3,0,0,0,0,3237960220,2130690484,297056159,3694463533,303476880,0,16384,3302357025,2064935920,245217319,3776455865,274003881,0,16384,4106268671,4130185751,69182517,4179658584,365641401,4294967292,25468 diff --git a/tools/ctl/ipc4/eq_iir/pass.txt b/tools/ctl/ipc4/eq_iir/pass.txt new file mode 100644 index 000000000000..ee0d545ef39e --- /dev/null +++ b/tools/ctl/ipc4/eq_iir/pass.txt @@ -0,0 +1 @@ +3,120,877023059,0,88,50450433,0,0,0,0,88,2,1,0,0,0,0,4294967295,4294967295,1,1,0,0,0,0,0,0,0,0,538145694,0,32690 diff --git a/tools/debug_stream/debug_stream.py b/tools/debug_stream/debug_stream.py new file mode 100644 index 000000000000..9c3706d90f99 --- /dev/null +++ b/tools/debug_stream/debug_stream.py @@ -0,0 +1,603 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: BSD-3-Clause +# +# Copyright (c) 2024, Intel Corporation. + +""" +For receiving, decoding and printing debug-stream records over debug window slot. +""" + +import argparse +import ctypes +import time +import sys +import os + +import logging + +logging.basicConfig( + format="%(filename)s:%(lineno)s %(funcName)s: %(message)s", level=logging.WARNING +) + +DEBUG_STREAM_PAYLOAD_MAGIC = 0x1ED15EED +DEBUG_SLOT_SIZE = 4096 + +# TODO: python construct would probably be cleaner than ctypes structs + +class DebugStreamHdr(ctypes.Structure): + """ + Generic Debug-stream header + """ + + _fields_ = [ + ("magic", ctypes.c_uint), + ("hdr_size", ctypes.c_uint), + ] + + +class DebugStreamRecord(ctypes.Structure): + """ + Debug Stream record for passing debug data + """ + + _fields_ = [ + ("id", ctypes.c_uint), + ("seqno", ctypes.c_uint), + ("size_words", ctypes.c_uint), + ] + + +class CPUInfo(ctypes.Structure): + """ + Thread Info record header + """ + + _pack_ = 1 + _fields_ = [ + ("hdr", DebugStreamRecord), + ("load", ctypes.c_ubyte), + ("thread_count", ctypes.c_ubyte), + ] + + +class ThreadInfo(ctypes.Structure): + """ + Thread specific data-record, the thread name string starts after name_len + """ + + _pack_ = 1 + _fields_ = [ + ("stack_usage", ctypes.c_ubyte), + ("cpu_load", ctypes.c_ubyte), + ("name_len", ctypes.c_ubyte), + ] + + +WSIZE = ctypes.sizeof(ctypes.c_uint) + + +class RecordPrinter: + """ + Debug Stream record decoder and printer class + """ + + RECORD_ID_UNINITIALIZED = 0 + RECORD_ID_THREAD_INFO = 1 + + def print_record(self, record, cpu): + """prints debug-stream record""" + recp = ctypes.cast(record, ctypes.POINTER(DebugStreamRecord)) + logging.debug( + "rec: %u %u %u", recp.contents.id, recp.contents.seqno, recp.contents.size_words + ) + if recp.contents.id == self.RECORD_ID_THREAD_INFO: + return self.print_thread_info(record, cpu) + logging.warning("cpu %u: Unsupported recodrd type %u", cpu, recp.contents.id) + return True + + def print_thread_info(self, record, cpu): + """prints thread-info record""" + remlen = len(record) - ctypes.sizeof(CPUInfo) + if remlen < 0: + logging.info("Buffer end reached, parsing failed") + return False + cpup = ctypes.cast(record, ctypes.POINTER(CPUInfo)) + print( + "CPU %u: Load: %02.1f%% %u threads (seqno %u)" + % ( + cpu, + cpup.contents.load / 2.55, + cpup.contents.thread_count, + cpup.contents.hdr.seqno, + ) + ) + remain = (ctypes.c_ubyte * (len(record) - ctypes.sizeof(CPUInfo))).from_address( + ctypes.addressof(record) + ctypes.sizeof(CPUInfo) + ) + for i in range(cpup.contents.thread_count): + remlen = remlen - ctypes.sizeof(ThreadInfo) + if remlen < 0: + logging.info("Buffer end reached, parsing failed on %u thread field", i) + return False + threadp = ctypes.cast(remain, ctypes.POINTER(ThreadInfo)) + remain = ( + ctypes.c_ubyte * (len(remain) - ctypes.sizeof(ThreadInfo)) + ).from_address(ctypes.addressof(remain) + ctypes.sizeof(ThreadInfo)) + remlen = remlen - threadp.contents.name_len + if remlen < 0: + logging.info("Buffer end reached, parsing failed on %u thread field", i) + return False + name = bytearray(remain[: threadp.contents.name_len]).decode("utf-8") + remain = ( + ctypes.c_ubyte * (len(remain) - threadp.contents.name_len) + ).from_address(ctypes.addressof(remain) + threadp.contents.name_len) + print( + " %-20s stack %02.1f%%\tload %02.1f%%" + % ( + name, + threadp.contents.stack_usage / 2.55, + threadp.contents.cpu_load / 2.55, + ) + ) + return True + + +class DebugStreamSectionDescriptor(ctypes.Structure): + """ + Describes CPU specific circular buffers + """ + + _fields_ = [ + ("core_id", ctypes.c_uint), + ("buf_words", ctypes.c_uint), + ("offset", ctypes.c_uint), + ] + + +class DebugStreamSlotHdr(ctypes.Structure): + """ + Debug Slot transport specific Debug Stream header, padded to cache line + """ + + _fields_ = [ + ("hdr", DebugStreamHdr), + ("total_size", ctypes.c_uint), + ("num_sections", ctypes.c_uint), + ] + + +class CircularBufHdr(ctypes.Structure): + """ + Live data header for CPU specific circular buffer + """ + + _fields_ = [ + ("next_seqno", ctypes.c_uint), + ("w_ptr", ctypes.c_uint), + ] + + +class CircularBufferDecoder: + """ + Class for extracting records from circular buffer + """ + + desc = None + boffset = None + buf_words = None + cpu = None + printer = None + prev_w_ptr = 0 + prev_seqno = None + error_count = 0 + + def __init__(self, desc, cpu, printer): + self.desc = desc + self.boffset = desc.offset + ctypes.sizeof(CircularBufHdr) + self.buf_words = desc.buf_words + self.cpu = cpu + self.printer = printer + logging.debug( + "boffset %u buf_words %u cpu %u", self.boffset, self.buf_words, self.cpu + ) + + def get_hdr(self, slot, pos): + """ + Get record header from position (handles circular buffer wrap) + """ + if pos >= self.buf_words: + logging.warning("Bad position %u", pos) + return None + hdr_size = ctypes.sizeof(DebugStreamRecord) + hdr_words = hdr_size // WSIZE + if pos + hdr_words > self.buf_words: + hdr = (ctypes.c_ubyte * hdr_size)() + size1 = (self.buf_words - pos) * WSIZE + size2 = hdr_size - size1 + pos1 = self.boffset + pos * WSIZE + pos2 = self.boffset + logging.debug( + "Wrapped header %u %u %u %u", pos, hdr_words, self.buf_words, size1 + ) + + hdr[0:size1] = slot[pos1 : pos1 + size1] + hdr[size1:hdr_size] = slot[pos2 : pos2 + size2] + header = ctypes.cast(hdr, ctypes.POINTER(DebugStreamRecord)).contents + else: + header = ctypes.cast( + slot[self.boffset + pos * WSIZE :], ctypes.POINTER(DebugStreamRecord) + ).contents + if header.id > 100 or header.size_words >= self.buf_words: + logging.info( + "Broken record id %u seqno %u size %u", + header.id, + header.seqno, + header.size_words, + ) + return None + return header + + def get_record(self, slot, pos, seqno): + """ + Get record from position + """ + rec = self.get_hdr(slot, pos) + if rec is None or rec.size_words == 0: + return None + logging.debug( + "got header at pos %u rec %u %u %u", pos, rec.id, rec.seqno, rec.size_words + ) + if seqno is not None and rec.seqno != seqno: + logging.warning( + "Record seqno mismatch %u != %u, pos %u size %u", + rec.seqno, + seqno, + pos, + rec.size_words, + ) + self.error_count = self.error_count + 1 + return None + rwords = rec.size_words + rsize = rec.size_words * WSIZE + if pos + rwords > self.buf_words: + record = (ctypes.c_ubyte * rsize)() + size1 = (self.buf_words - pos) * WSIZE + size2 = rsize - size1 + pos1 = self.boffset + pos * WSIZE + pos2 = self.boffset + logging.debug( + "Wrapped record %u %u %u %u", pos, rsize, self.buf_words, size1 + ) + + record[0:size1] = slot[pos1 : pos1 + size1] + record[size1:rsize] = slot[pos2 : pos2 + size2] + else: + record = (ctypes.c_ubyte * rsize).from_buffer_copy( + slot, self.boffset + pos * WSIZE + ) + logging.info("got %u", rec.seqno) + self.error_count = 0 + return record + + def catch_up(self, slot): + """ + Search backwards from w_ptr for valid records + """ + circ = CircularBufHdr.from_buffer_copy(slot, self.desc.offset) + if circ.next_seqno == 0 or circ.w_ptr >= self.buf_words: + return + self.decode_past_records(slot, circ.w_ptr, circ.next_seqno) + self.prev_w_ptr = circ.w_ptr + self.prev_seqno = circ.next_seqno - 1 + logging.debug("seqno %u w_ptr %u", self.prev_seqno, self.prev_w_ptr) + + def decode_past_records(self, slot, pos, seqno): + """ + Decode records backwards from pos. Should not be called directly, use catch_up() + """ + if self.prev_seqno is not None and self.prev_seqno >= seqno - 1: + return + if pos == 0: + spos = self.buf_words - 1 + else: + spos = pos - 1 + bsize = ctypes.cast( + slot[self.boffset + spos * 4 :], ctypes.POINTER(ctypes.c_uint) + ).contents.value + bpos = pos - bsize + if bpos < 0: + bpos = self.buf_words + pos - bsize + rec = self.get_hdr(slot, bpos) + if rec == None: + return + if bsize != rec.size_words: + return + if seqno is not None: + if rec.seqno != seqno - 1: + return + else: + seqno = rec.seqno + 1 + + self.decode_past_records(slot, bpos, seqno - 1) + + record = self.get_record(slot, bpos, seqno - 1) + if record is not None: + if not self.printer.print_record(record, self.cpu): + logging.info("Parse failed on record %u", seqno - 1) + logging.info("Printing %u success", seqno - 1) + else: + logging.info("Broken record %u", seqno - 1) + + def get_next_record(self, slot): + """ + Get next record from the circular buffer and print it, returns True if a record + with expected seqno number was found and successfully decoded and printed. + """ + if self.prev_seqno is not None: + record = self.get_record(slot, self.prev_w_ptr, self.prev_seqno + 1) + else: + record = self.get_record(slot, self.prev_w_ptr, None) + if record is not None: + print_success = self.printer.print_record(record, self.cpu) + if print_success: + recp = ctypes.cast(record, ctypes.POINTER(DebugStreamRecord)) + self.prev_w_ptr = ( + self.prev_w_ptr + recp.contents.size_words + ) % self.buf_words + self.prev_seqno = recp.contents.seqno + else: + logging.info("Parse failed on record %u", self.prev_seqno + 1) + return print_success + self.error_count = self.error_count + 1 + logging.info("Record decoding failed %u", self.error_count) + return False + + def poll_buffer(self, slot): + """ + Poll for new records. If there were new records return True + """ + circ = CircularBufHdr.from_buffer_copy(slot, self.desc.offset) + if self.prev_w_ptr == circ.w_ptr: + return False + got_record = True + while self.prev_w_ptr != circ.w_ptr and got_record: + got_record = self.get_next_record(slot) + return True + + def check_error_count(self): + """ + Check if maximum error count was reached + """ + if self.error_count > 3: + return True + return False + + +class DebugStreamDecoder: + """ + Class for decoding debug-stream slot contents + """ + + file_size = DEBUG_SLOT_SIZE + file = None + slot = None + descs = [] + circdec = [] + rec_printer = RecordPrinter() + + def set_file(self, file): + """ + Ser file to read the slot contents from + """ + self.file = file + + def update_slot(self): + """ + Update slot contents + """ + self.file.seek(0) + self.slot = self.file.read(self.file_size) + + def set_slot(self, buf): + """ + Update slot contents + """ + self.slot = buf + + def get_descriptors(self): + """ + Read the core specific descriptors and initialize core + specific circular buffer decoders. + + """ + if self.slot is None: + return False + hdr = ctypes.cast(self.slot, ctypes.POINTER(DebugStreamSlotHdr)) + if hdr.contents.hdr.magic != DEBUG_STREAM_PAYLOAD_MAGIC: + logging.info("Debug Slot has bad magic 0x%08x", hdr.contents.hdr.magic) + return False + num_sections = hdr.contents.num_sections + if num_sections == len(self.descs): + return True + if num_sections > 32: + logging.info("Suspiciously many sections %u", num_sections) + return False + hsize = ctypes.sizeof(DebugStreamSlotHdr) + self.descs = (DebugStreamSectionDescriptor * num_sections).from_buffer_copy( + self.slot, hsize + ) + for i in range(len(self.descs)): + if (self.descs[i].core_id > 32 or + self.descs[i].buf_words > DEBUG_SLOT_SIZE // WSIZE or + self.descs[i].offset > DEBUG_SLOT_SIZE): + logging.info("Suspicious descriptor %u values %u %u %u", i, + self.descs[i].core_id, self.descs[i].buf_words, + self.descs[i].offset) + return False + self.circdec = [ + CircularBufferDecoder(self.descs[i], i, self.rec_printer) + for i in range(len(self.descs)) + ] + logging.info( + "Header hdr_size %u total_size %u num_sections %u", + hdr.contents.hdr.hdr_size, + hdr.contents.total_size, + hdr.contents.num_sections, + ) + return True + + def catch_up_all(self): + """ + Checks all circular buffers if there is records behind w_ptr. If there + is valid seqno number available the decoding stops when there previous + printed record is reached. + """ + if len(self.descs) == 0 or self.slot is None: + return + for i in range(len(self.descs)): + self.circdec[i].catch_up(self.slot) + + def poll(self): + """ + Poll all circular buffers for more records. Returns True if nothing new + was found and its time to go to sleep. + """ + if len(self.descs) == 0 or self.slot is None: + return False + sleep = True + for i in range(len(self.descs)): + if self.circdec[i].poll_buffer(self.slot): + sleep = False + return sleep + + def check_slot(self): + """ + Check if previously updated slot contents is valid + """ + hdr = ctypes.cast(self.slot, ctypes.POINTER(DebugStreamSlotHdr)) + if hdr.contents.hdr.magic != DEBUG_STREAM_PAYLOAD_MAGIC: + self.slot = None + return False + if hdr.contents.num_sections != len(self.descs): + self.slot = None + return False + for i in range(len(self.descs)): + if self.circdec[i].check_error_count(): + self.circdec[i] = CircularBufferDecoder( + self.descs[i], i, self.rec_printer + ) + return True + + def reset(self): + """ + Reset decoder + """ + self.file = None + self.slot = None + +def cavstool_main_loop(my_args): + import cavstool + try: + (hda, sd, dsp, hda_ostream_id) = cavstool.map_regs(True) + except Exception as e: + logging.error("Could not map device in sysfs; run as root?") + logging.error(e) + sys.exit(1) + ADSP_DW_SLOT_DEBUG_STREAM = 0x53523134 + decoder = DebugStreamDecoder() + while True: + if not cavstool.fw_is_alive(dsp): + cavstool.wait_fw_entered(dsp, timeout_s=None) + if my_args.direct_access_slot < 0: + offset = cavstool.debug_slot_offset_by_type(ADSP_DW_SLOT_DEBUG_STREAM, timeout_s=0.5) + if offset is None: + logging.error("Could not find debug_stream slot") + sys.exit(1) + logging.info("Got offset 0x%08x by type 0x%08x", offset, + ADSP_DW_SLOT_DEBUG_STREAM) + else: + offset = cavstool.debug_slot_offset(my_args.direct_access_slot) + buf = cavstool.win_read(offset, 0, DEBUG_SLOT_SIZE) + decoder.set_slot(buf) + if not decoder.get_descriptors(): + time.sleep(my_args.update_interval) + continue + decoder.catch_up_all() + while True: + if decoder.poll(): + time.sleep(my_args.update_interval) + buf = cavstool.win_read(offset, 0, DEBUG_SLOT_SIZE) + decoder.set_slot(buf) + if not decoder.check_slot(): + break + +def main_f(my_args): + """ + Open debug stream slot file and pass it to decoder + + This tool and the protocol it implements is for debugging and optimized + to interfere with SOF DSP as little as possible, but not worrying too much + about the host CPU load. That is why there where no synchronous mechanism + done and the host simply polls for new records. + """ + if my_args.direct_access_slot >= 0 or not os.path.isfile(my_args.debugstream_file): + return cavstool_main_loop(my_args) + decoder = DebugStreamDecoder() + prev_error = None + while True: + try: + with open(my_args.debugstream_file, "rb") as file: + decoder.set_file(file) + decoder.update_slot() + if not decoder.get_descriptors(): + time.sleep(my_args.update_interval) + continue + decoder.catch_up_all() + while True: + if decoder.poll(): + time.sleep(my_args.update_interval) + decoder.update_slot() + if not decoder.check_slot(): + break + + except FileNotFoundError: + print(f"File {my_args.debugstream_file} not found!") + break + except OSError as err: + if str(err) != prev_error: + print(f"Open {my_args.debugstream_file} failed '{err}'") + prev_error = str(err) + decoder.reset() + time.sleep(args.update_interval) + + +def parse_params(): + """Parses parameters""" + parser = argparse.ArgumentParser(description="SOF DebugStream thread info client. ") + parser.add_argument( + "-t", + "--update-interval", + type=float, + help="Telemetry2 window polling interval in seconds, default 1", + default=0.01, + ) + parser.add_argument( + "-f", + "--debugstream-file", + help="File to read the DebugStream data from, default /sys/kernel/debug/sof/debug_stream", + default="/sys/kernel/debug/sof/debug_stream", + ) + parser.add_argument( + "-c", + "--direct-access-slot", + help="Access specified debug window slot directly, no need for debugfs file", + type=int, + default=-1, + ) + parsed_args = parser.parse_args() + return parsed_args + + +if __name__ == "__main__": + args = parse_params() + main_f(args) diff --git a/tools/demo-gui/README-dev.md b/tools/demo-gui/README-dev.md new file mode 100644 index 000000000000..455d3bf6b582 --- /dev/null +++ b/tools/demo-gui/README-dev.md @@ -0,0 +1,40 @@ +## Developing sof-gui and tui + +The architecture of the SOF UIs is simple, and designed to make implementing new features extremely easy. + +Controller engine "sof_controller_engine.py", that handles all interaction with Linux and SOF. + +GUI "sof_demo_gui.py", links a GTK gui with the controller engine. + +TUI "sof_demo_tui.py", links a text UI to the controller engine. + +eq_configs and audios folders to contain example audios and EQ commands. + +Pipeline within topology folder, named: +```sof--gui-components-wm8960.tplg``` + +## Adding a new component to the UIs + +There are three main things that need to be edited to add a new component: + +### Controller engine + +Provide required sof_ctl calls and other necessary logic to control the component. Update execute_command to contain the desired commands. Ensure that autodetection is used for commands so that the implementation is generic. + +### GUI and TUI + +Add new buttons to the init method or gui that provide the needed functionality for the component. These should be designed to call methods in controller engine that will then interact with SOF and Linux + +### Pipeline + +See relevant documentation for pipeline development. Ensure any control needed is exposed through the pipeline. Also ensure the pipeline is set to build for your target HW within the cmakefiles. + +## Next steps for overall UI development + +Add DRC and other base level SOF components. + +Add real time EQ config generation, so the user could control low, mid, and high controls in real time. This would require a new EQ component that supports smooth real time control. + +Add graphics and other quality of life functions to the GUI. + +Create a version of sof-ctl that provides direct Python bindings to communicate with SOF components, rather than needing a Linux command. diff --git a/tools/demo-gui/README.md b/tools/demo-gui/README.md new file mode 100644 index 000000000000..279937dfa0aa --- /dev/null +++ b/tools/demo-gui/README.md @@ -0,0 +1,37 @@ +## sof-demo-gui + +### sof-demo-gui.py - sof-demo-tui.py +User input logic and display handling + +### sof-controller-engine +Controller to abstract the GUI and TUI control. + +Handles the linking of user input and sof_ctl generically of the control type. + +### How to use the interfaces + +Build sof-ctl for target and copy it to the gui folder base directory within your local repo. + +If you have audio on your local machine that you wish to demonstrate using the GUI, add it to the audios subfolder. +Also, you can specify audio paths on the target with the command line arg --audio-path "path" + +If you would like to include eq configs, they are stored in tools/ctl/ipc3. Copy them from there to the eq_configs folder. + +Copy entire GUI folder to target hardware. + +Next, ensure that the +```sof--gui-components-wm8960.tplg``` +is built and loaded as SOF's topology. Make sure this is built for your target hardware. + +After this, run either the GUI or TUI on a board with SOF loaded. This can be done using the command: +```python3 sof-demo-gui``` +or +```python3 sof-demo-tui``` + +The interfaces themselves are self-explanatory, as they are made to be plug and play on all SOF supporting systems. + +The features currently supported are: +Playback and Record with ALSA +Volume control using the SOF component +EQ component with realtime control +Generic implementation with autodetection of SOF cards and commands diff --git a/tools/demo-gui/demo-gui/sof_controller_engine.py b/tools/demo-gui/demo-gui/sof_controller_engine.py new file mode 100644 index 000000000000..c1dd6c1d9294 --- /dev/null +++ b/tools/demo-gui/demo-gui/sof_controller_engine.py @@ -0,0 +1,243 @@ +# SPDX-License-Identifier: BSD-3-Clause + +import subprocess +import os +import signal +import re +import math + +# Global variables +aplay_process = None +arecord_process = None +paused = None +current_file = None +device_string = None +volume_control = None +eq_numid = None +is_muted = False +previous_volume = 50 # Store the previous volume level +extra_audio_paths = [] + +def initialize_device(): + global device_string, volume_control, eq_numid + + try: + output = subprocess.check_output(["aplay", "-l"], text=True, stderr=subprocess.DEVNULL) + match = re.search(r"card (\d+):.*\[.*sof.*\]", output, re.IGNORECASE) + if match: + card_number = match.group(1) + device_string = f"hw:{card_number}" + print(f"Detected SOF card: {device_string}") + else: + raise RuntimeError("SOF card not found. Ensure the device is connected and recognized by the system.") + + controls_output = subprocess.check_output(["amixer", f"-D{device_string}", "controls"], text=True, stderr=subprocess.DEVNULL) + volume_match = re.search(r"numid=(\d+),iface=MIXER,name='(.*Master Playback Volume.*)'", controls_output) + if volume_match: + volume_control = volume_match.group(2) + print(f"Detected Volume Control: {volume_control}") + else: + raise RuntimeError("Volume control not found.") + + eq_match = re.search(r"numid=(\d+),iface=MIXER,name='EQIIR1\.0 eqiir_coef_1'", controls_output) + if eq_match: + eq_numid = eq_match.group(1) + print(f"Detected EQ numid: {eq_numid}") + else: + raise RuntimeError("EQ control not found.") + + except subprocess.CalledProcessError as e: + print(f"Failed to run device detection commands: {e}") + raise + except RuntimeError as e: + print(e) + raise + +def scale_volume(user_volume): + normalized_volume = user_volume / 100.0 + scaled_volume = 31 * math.sqrt(normalized_volume) + return int(round(scaled_volume)) + +def scan_for_files(directory_name: str, file_extension: str, extra_paths: list = None): + found_files = [] + dir_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), directory_name) + + try: + if os.path.exists(dir_path): + found_files.extend([f for f in os.listdir(dir_path) if f.endswith(file_extension)]) + else: + print(f"Error: The '{directory_name}' directory is missing. It should be located in the same folder as this script.") + + if extra_paths: + for path in extra_paths: + if os.path.exists(path): + found_files.extend([f for f in os.listdir(path) if f.endswith(file_extension)]) + else: + print(f"Warning: The directory '{path}' does not exist.") + except Exception as e: + print(f"Error scanning for files: {e}") + + return found_files + +def execute_command(command: str, data: int = 0, file_name: str = None): + if device_string is None or volume_control is None or eq_numid is None: + raise RuntimeError("Device not initialized. Call initialize_device() first.") + + command_switch = { + 'volume': lambda x: handle_volume(data), + 'eq': lambda x: handle_eq(file_name), + 'play': lambda x: handle_play(file_name), + 'pause': lambda x: handle_pause(), + 'stop': lambda x: handle_stop(), + 'mute': lambda x: handle_mute(), + 'record': lambda x: handle_record(start=data, filename=file_name) + } + + command_function = command_switch.get(command, lambda x: handle_unknown_command(data)) + try: + command_function(data) + except Exception as e: + print(f"Error executing command '{command}': {e}") + +def handle_volume(data: int): + amixer_command = f"amixer -D{device_string} cset name='{volume_control}' {data}" + try: + subprocess.run(amixer_command, shell=True, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + except subprocess.CalledProcessError as e: + print(f"Failed to set volume: {e}") + +def handle_eq(eq_file_name: str): + if not eq_file_name: + print("No EQ file name provided.") + return + + ctl_command = f"./sof-ctl -D{device_string} -n {eq_numid} -s {eq_file_name}" + try: + subprocess.run(ctl_command, shell=True, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + except subprocess.CalledProcessError as e: + print(f"Failed to apply EQ settings: {e}") + +def handle_play(play_file_name: str): + global aplay_process, paused, current_file + + if not play_file_name: + print("No file name provided for playback.") + return + + try: + if paused and current_file == play_file_name: + os.kill(aplay_process.pid, signal.SIGCONT) + print("Playback resumed.") + paused = False + return + + if aplay_process is not None: + if aplay_process.poll() is None: + if current_file == play_file_name: + print("Playback is already in progress.") + return + else: + os.kill(aplay_process.pid, signal.SIGKILL) + print("Stopping current playback to play a new file.") + else: + print("Previous process is not running, starting new playback.") + + default_audio_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'audios') + file_path = next((os.path.join(path, play_file_name) for path in [default_audio_dir] + extra_audio_paths if os.path.exists(os.path.join(path, play_file_name))), None) + + if file_path is None: + print(f"Error: File '{play_file_name}' not found in the default 'audios' directory or any provided paths.") + return + + aplay_command = f"aplay -D{device_string} '{file_path}'" + aplay_process = subprocess.Popen(aplay_command, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + current_file = play_file_name + print(f"Playing file: {play_file_name}.") + paused = False + except subprocess.CalledProcessError as e: + print(f"Failed to play file: {e}") + except Exception as e: + print(f"Error during playback: {e}") + +def handle_pause(): + global aplay_process, paused + + if aplay_process is None: + print("No playback process to pause.") + return + + if aplay_process.poll() is not None: + print("Playback process has already finished.") + return + + try: + os.kill(aplay_process.pid, signal.SIGSTOP) + paused = True + print("Playback paused.") + except Exception as e: + print(f"Failed to pause playback: {e}") + +def handle_stop(): + global aplay_process, paused, current_file + + if aplay_process is None: + print("No playback process to stop.") + return + + if aplay_process.poll() is not None: + print("Playback process has already finished.") + return + + try: + os.kill(aplay_process.pid, signal.SIGKILL) + aplay_process = None + paused = False + current_file = None + print("Playback stopped.") + except Exception as e: + print(f"Failed to stop playback: {e}") + +def handle_mute(): + if not is_muted: + previous_volume = scale_volume(50) # Assume 50 is the current volume + handle_volume(0) + is_muted = True + print("Audio muted.") + else: + handle_volume(previous_volume) + is_muted = False + print("Audio unmuted.") + +def handle_record(start: bool, filename: str): + global arecord_process + + if start: + if arecord_process is not None and arecord_process.poll() is None: + print("Recording is already in progress.") + return + + if not filename: + print("No filename provided for recording.") + return + + record_command = f"arecord -D{device_string} -f cd -t wav {filename}" + try: + arecord_process = subprocess.Popen(record_command, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + print(f"Started recording: {filename}") + except subprocess.CalledProcessError as e: + print(f"Failed to start recording: {e}") + + else: + if arecord_process is None or arecord_process.poll() is not None: + print("No recording process to stop.") + return + + try: + os.kill(arecord_process.pid, signal.SIGINT) + arecord_process = None + print("Stopped recording.") + except Exception as e: + print(f"Failed to stop recording: {e}") + +def handle_unknown_command(data: int): + print(f"Unknown command: {data}") diff --git a/tools/demo-gui/demo-gui/sof_demo_gui.py b/tools/demo-gui/demo-gui/sof_demo_gui.py new file mode 100644 index 000000000000..3f18287adf2a --- /dev/null +++ b/tools/demo-gui/demo-gui/sof_demo_gui.py @@ -0,0 +1,257 @@ +# SPDX-License-Identifier: BSD-3-Clause + +import gi +import os +import argparse +import sof_controller_engine as sof_ctl +gi.require_version("Gtk", "3.0") +from gi.repository import Gtk, Gdk + +class MyWindow(Gtk.Window): + def __init__(self, audio_paths, config_paths): + super().__init__(title="SOF Demo Gui") + self.set_resizable(False) + self.set_border_width(10) + self.apply_css() + self.init_ui(audio_paths, config_paths) + sof_ctl.initialize_device() + self.is_muted = False + self.previous_volume = 50 # Store the previous volume level + + def apply_css(self): + css_provider = Gtk.CssProvider() + css = """ + * { + font-family: "Segoe UI", "Arial", sans-serif; + font-size: 14px; + color: #020202; + } + window { + background-color: #2C3E50; + } + frame { + border: 1px solid #34495E; + border-radius: 5px; + padding: 10px; + margin: 5px; + background-color: #34495E; + } + button, togglebutton { + background-color: #3b3b3b; + border: none; + padding: 10px; + color: #020202; + } + button:hover, togglebutton:hover { + background-color: #A9A9A9; + color: #020202; + } + togglebutton:checked { + background-color: #A9A9A9; + color: #020202; + } + scale { + background-color: #34495E; + border-radius: 5px; + color: #FFFFFF; + } + label { + color: #020202; + } + headerbar { + background-color: #2C3E50; + color: #FFFFFF; + } + headerbar.titlebar { + background-color: #2C3E50; + } + """ + css_provider.load_from_data(css.encode('utf-8')) + screen = Gdk.Screen.get_default() + Gtk.StyleContext.add_provider_for_screen(screen, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) + + def init_ui(self, audio_paths, config_paths): + main_grid = Gtk.Grid() + main_grid.set_row_spacing(10) + main_grid.set_column_spacing(10) + self.add(main_grid) + + control_frame = self.create_control_frame() + main_grid.attach(control_frame, 0, 0, 1, 1) + + file_frame = self.create_file_frame() + main_grid.attach(file_frame, 0, 1, 1, 1) + + record_frame = self.create_record_frame() + main_grid.attach(record_frame, 0, 2, 1, 1) + + self.scan_and_populate_dropdowns(audio_paths, config_paths) + + def create_control_frame(self): + control_frame = Gtk.Frame(label="Playback and Volume Control") + control_grid = Gtk.Grid() + control_grid.set_row_spacing(10) + control_grid.set_column_spacing(10) + control_frame.add(control_grid) + + volume_adjustment = Gtk.Adjustment(value=100, lower=0, upper=100, step_increment=1, page_increment=5, page_size=0) + self.volume_button = Gtk.Scale(name="volume", orientation=Gtk.Orientation.HORIZONTAL, adjustment=volume_adjustment) + self.volume_button.set_digits(0) + self.volume_button.set_hexpand(True) + self.volume_button.connect("value-changed", self.on_volume_changed) + control_grid.attach(self.volume_button, 0, 0, 2, 1) + + self.play_pause_button = Gtk.ToggleButton(label="Play") + self.play_pause_button.connect("toggled", self.on_play_pause_toggled) + control_grid.attach(self.play_pause_button, 0, 1, 1, 1) + + self.stop_button = Gtk.Button(label="Stop") + self.stop_button.connect("clicked", self.on_stop_clicked) + control_grid.attach(self.stop_button, 1, 1, 1, 1) + + self.mute_button = Gtk.Button(label="Mute") + self.mute_button.connect("clicked", self.on_mute_clicked) + control_grid.attach(self.mute_button, 0, 2, 2, 1) + + return control_frame + + def create_file_frame(self): + file_frame = Gtk.Frame(label="File Selection") + file_grid = Gtk.Grid() + file_grid.set_row_spacing(10) + file_grid.set_column_spacing(10) + file_frame.add(file_grid) + + self.wav_dropdown = Gtk.ComboBoxText() + self.wav_dropdown.connect("changed", self.on_wav_file_selected) + wav_label = Gtk.Label(label="Select WAV File") + wav_label.set_margin_top(5) + wav_label.set_margin_bottom(5) + file_grid.attach(wav_label, 0, 0, 1, 1) + file_grid.attach(self.wav_dropdown, 1, 0, 1, 1) + + self.eq_dropdown = Gtk.ComboBoxText() + eq_label = Gtk.Label(label="Select EQ Config") + eq_label.set_margin_top(5) + eq_label.set_margin_bottom(5) + file_grid.attach(eq_label, 0, 1, 1, 1) + file_grid.attach(self.eq_dropdown, 1, 1, 1, 1) + + self.apply_eq_button = Gtk.Button(label="Apply EQ Config") + self.apply_eq_button.connect("clicked", self.on_apply_eq_clicked) + file_grid.attach(self.apply_eq_button, 1, 2, 1, 1) + + return file_frame + + def create_record_frame(self): + record_frame = Gtk.Frame(label="Recording Control") + record_grid = Gtk.Grid() + record_grid.set_row_spacing(10) + record_grid.set_column_spacing(10) + record_grid.set_hexpand(True) + record_grid.set_vexpand(True) + record_frame.add(record_grid) + + self.record_button = Gtk.ToggleButton(label="Record") + self.record_button.connect("toggled", self.on_record_toggled) + self.record_button.set_hexpand(True) + self.record_button.set_vexpand(True) + record_grid.attach(self.record_button, 0, 0, 1, 1) + + self.record_index = 1 + + return record_frame + + def scan_and_populate_dropdowns(self, audio_paths, config_paths): + wav_files = sof_ctl.scan_for_files('audios', '.wav', extra_paths=audio_paths) + + self.wav_dropdown.remove_all() + for wav_file in wav_files: + self.wav_dropdown.append_text(wav_file) + + if wav_files: + self.wav_dropdown.set_active(0) + self.selected_wav_file = wav_files[0] + + eq_files = sof_ctl.scan_for_files('eq_configs', '.txt', extra_paths=config_paths) + + self.eq_dropdown.remove_all() + for eq_file in eq_files: + self.eq_dropdown.append_text(eq_file) + + if eq_files: + self.eq_dropdown.set_active(0) + self.selected_eq_file = eq_files[0] + + def on_volume_changed(self, widget): + user_volume = widget.get_value() + scaled_volume = sof_ctl.scale_volume(user_volume) + sof_ctl.execute_command(command="volume", data=scaled_volume) + + def on_play_pause_toggled(self, widget): + if widget.get_active(): + widget.set_label("Pause") + sof_ctl.execute_command(command="play", file_name=self.selected_wav_file) + else: + widget.set_label("Play") + sof_ctl.execute_command(command="pause") + + def on_stop_clicked(self, widget): + self.play_pause_button.set_active(False) + sof_ctl.execute_command(command="stop") + + def on_mute_clicked(self, widget): + if not self.is_muted: + self.previous_volume = self.volume_button.get_value() + self.volume_button.set_value(0) + self.is_muted = True + widget.set_label("Unmute") + else: + self.volume_button.set_value(self.previous_volume) + self.is_muted = False + widget.set_label("Mute") + + def on_record_toggled(self, widget): + if widget.get_active(): + widget.set_label("Stop Recording") + filename = self.generate_sequential_filename() + sof_ctl.execute_command(command="record", data=True, file_name=filename) + else: + widget.set_label("Record") + sof_ctl.execute_command(command="record", data=False) + + def generate_sequential_filename(self): + recordings_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'recordings') + + if not os.path.exists(recordings_dir): + os.makedirs(recordings_dir) + + filename = os.path.join(recordings_dir, f"recording_{self.record_index}.wav") + self.record_index += 1 + + return filename + + def on_wav_file_selected(self, widget): + self.selected_wav_file = widget.get_active_text() + + def on_apply_eq_clicked(self, widget): + self.selected_eq_file = self.eq_dropdown.get_active_text() + if self.selected_eq_file: + eq_file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'eq_configs', self.selected_eq_file) + sof_ctl.execute_command(command="eq", file_name=eq_file_path) + +def main(): + parser = argparse.ArgumentParser(description="SOF GUI Application") + parser.add_argument('--audio-path', action='append', help="Additional path to search for audio files (.wav)") + parser.add_argument('--config-path', action='append', help="Additional path to search for EQ config files (.txt)") + args = parser.parse_args() + + sof_ctl.extra_audio_paths = args.audio_path if args.audio_path else [] + + win = MyWindow(audio_paths=args.audio_path, config_paths=args.config_path) + win.connect("destroy", Gtk.main_quit) + win.show_all() + Gtk.main() + +if __name__ == "__main__": + main() diff --git a/tools/demo-gui/demo-gui/sof_demo_tui.py b/tools/demo-gui/demo-gui/sof_demo_tui.py new file mode 100644 index 000000000000..3fb387d684e1 --- /dev/null +++ b/tools/demo-gui/demo-gui/sof_demo_tui.py @@ -0,0 +1,184 @@ +# SPDX-License-Identifier: BSD-3-Clause + +import os +import sof_controller_engine as sof_ctl +import argparse + +os.chdir(os.path.dirname(os.path.abspath(__file__))) + +class TextBasedUI: + def __init__(self, audio_paths, config_paths): + self.selected_wav_file = None + self.selected_eq_file = None + self.volume = 15 + self.is_playing = False + self.is_recording = False + self.scan_and_populate_files(audio_paths, config_paths) + sof_ctl.initialize_device() + self.run_ui() + + def scan_and_populate_files(self, audio_paths, config_paths): + self.wav_files = sof_ctl.scan_for_files('audios', '.wav', extra_paths=audio_paths) + + if self.wav_files: + self.selected_wav_file = self.wav_files[0] + print(f"Default WAV file selected: {self.selected_wav_file}") + + self.eq_files = sof_ctl.scan_for_files('eq_configs', '.txt', extra_paths=config_paths) + + if self.eq_files: + self.selected_eq_file = self.eq_files[0] + print(f"Default EQ config selected: {self.selected_eq_file}") + + def display_menu(self): + print("\nSOF Text-Based UI") + print("==================") + print("1. Select a WAV file") + if self.selected_wav_file: + print(f" Currently selected: {self.selected_wav_file}") + else: + print(" No WAV file selected") + + print("2. Select an EQ Config") + if self.selected_eq_file: + print(f" Currently selected: {self.selected_eq_file}") + else: + print(" No EQ file selected") + + print(f"3. Set Volume (Current: {self.volume})") + print(f"4. Play/Pause (Current: {'Playing' if self.is_playing else 'Paused'})") + print(f"5. Start/Stop Recording (Current: {'Recording' if self.is_recording else 'Not Recording'})") + print("6. Apply EQ Config") + print("7. Quit") + print("==================") + + def run_ui(self): + while True: + self.display_menu() + choice = input("Enter your choice (1-7): ").strip() + + if choice == '1': + self.select_wav_file() + elif choice == '2': + self.select_eq_file() + elif choice == '3': + self.set_volume() + elif choice == '4': + self.toggle_play_pause() + elif choice == '5': + self.toggle_record() + elif choice == '6': + self.apply_eq_config() + elif choice == '7': + print("Exiting...") + break + else: + print("Invalid choice, please try again.") + + def select_wav_file(self): + if not self.wav_files: + print("No .wav files found in the current directory.") + return + + print("\nSelect a WAV file:") + for idx, wav_file in enumerate(self.wav_files): + print(f"{idx + 1}. {wav_file}") + + file_choice = input(f"Enter the number of the file (1-{len(self.wav_files)}): ").strip() + + try: + file_index = int(file_choice) - 1 + if 0 <= file_index < len(self.wav_files): + self.selected_wav_file = self.wav_files[file_index] + print(f"Selected file: {self.selected_wav_file}") + else: + print("Invalid selection.") + except ValueError: + print("Invalid input, please enter a number.") + + def select_eq_file(self): + if not self.eq_files: + print("No EQ config files found in the eq_configs directory.") + return + + print("\nSelect an EQ Config file:") + for idx, eq_file in enumerate(self.eq_files): + print(f"{idx + 1}. {eq_file}") + + file_choice = input(f"Enter the number of the file (1-{len(self.eq_files)}): ").strip() + + try: + file_index = int(file_choice) - 1 + if 0 <= file_index < len(self.eq_files): + self.selected_eq_file = self.eq_files[file_index] + print(f"Selected EQ Config: {self.selected_eq_file}") + else: + print("Invalid selection.") + except ValueError: + print("Invalid input, please enter a number.") + + def set_volume(self): + volume_input = input("Enter volume level (0-100): ").strip() + + try: + user_volume = int(volume_input) + if 0 <= user_volume <= 100: + self.volume = user_volume + scaled_volume = sof_ctl.scale_volume(user_volume) + sof_ctl.execute_command(command="volume", data=scaled_volume) + print(f"Volume set to {self.volume}.") + else: + print("Volume must be between 0 and 100.") + except ValueError: + print("Invalid input, please enter a number.") + + def toggle_play_pause(self): + if not self.selected_wav_file: + print("No WAV file selected.") + return + + if not self.is_playing: + sof_ctl.execute_command(command="play", file_name=self.selected_wav_file) + self.is_playing = True + print("Playing...") + else: + sof_ctl.execute_command(command="pause") + self.is_playing = False + print("Paused.") + + def toggle_record(self): + recordings_dir = os.path.join(os.getcwd(), 'recordings') + + if not self.is_recording: + filename = input(f"Enter filename to save the recording (default: {recordings_dir}/record.wav): ").strip() + if not filename: + filename = "record.wav" + + full_path = os.path.join(recordings_dir, filename) + sof_ctl.execute_command(command="record", data=True, file_name=full_path) + self.is_recording = True + else: + sof_ctl.execute_command(command="record", data=False) + self.is_recording = False + + def apply_eq_config(self): + if not self.selected_eq_file: + print("No EQ config file selected.") + return + + eq_file_path = os.path.join(os.getcwd(), 'eq_configs', self.selected_eq_file) + sof_ctl.execute_command(command="eq", file_name=eq_file_path) + print(f"Applied EQ config: {self.selected_eq_file}") + +def main(): + parser = argparse.ArgumentParser(description="SOF TUI Application") + parser.add_argument('--audio-path', action='append', help="Additional path to search for audio files (.wav)") + parser.add_argument('--config-path', action='append', help="Additional path to search for EQ config files (.txt)") + args = parser.parse_args() + + sof_ctl.extra_audio_paths = args.audio_path if args.audio_path else [] + + TextBasedUI(audio_paths=args.audio_path, config_paths=args.config_path) + +if __name__ == "__main__": + main() diff --git a/tools/logger/CMakeLists.txt b/tools/logger/CMakeLists.txt index d84c92ce0cc5..ac83f5687454 100644 --- a/tools/logger/CMakeLists.txt +++ b/tools/logger/CMakeLists.txt @@ -1,5 +1,13 @@ # SPDX-License-Identifier: BSD-3-Clause +# https://gitlab.kitware.com/cmake/community/-/wikis/doc/tutorials/How-To-Write-Platform-Checks +INCLUDE (CheckIncludeFiles) +CHECK_INCLUDE_FILES(sys/inotify.h HAS_INOTIFY) + +CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in + ${CMAKE_CURRENT_BINARY_DIR}/config.h) +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + add_executable(sof-logger logger.c convert.c @@ -7,6 +15,9 @@ add_executable(sof-logger misc.c ) +include(../../scripts/cmake/misc.cmake) +include(../../scripts/cmake/uuid-registry.cmake) + if(${CMAKE_HOST_WIN32}) cmake_minimum_required(VERSION 3.20) if(DEFINED ENV{MSYS_INSTALL_DIR}) @@ -27,8 +38,13 @@ target_compile_options(sof-logger PRIVATE target_include_directories(sof-logger PRIVATE "${SOF_ROOT_SOURCE_DIRECTORY}/src/include" - "${SOF_ROOT_SOURCE_DIRECTORY}/rimage/src/include" + "${SOF_ROOT_SOURCE_DIRECTORY}/tools/rimage/src/include" "${SOF_ROOT_SOURCE_DIRECTORY}" ) +# TODO: logger should not need to include RTOS headers. FIX. +target_include_directories(sof-logger PRIVATE + "${SOF_ROOT_SOURCE_DIRECTORY}/xtos/include" +) + install(TARGETS sof-logger DESTINATION bin) diff --git a/tools/logger/config.h.in b/tools/logger/config.h.in new file mode 100644 index 000000000000..fecbafbd274c --- /dev/null +++ b/tools/logger/config.h.in @@ -0,0 +1 @@ +#cmakedefine01 HAS_INOTIFY diff --git a/tools/logger/convert.c b/tools/logger/convert.c index 569f81579f52..4e0a6cdc59ae 100644 --- a/tools/logger/convert.c +++ b/tools/logger/convert.c @@ -60,13 +60,11 @@ struct proc_ldc_entry { uintptr_t params[TRACE_MAX_PARAMS_COUNT]; }; -static const char *BAD_PTR_STR = ""; - +#define BAD_PTR_STR "" #define UUID_LOWER "%s%s%s<%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x>%s%s%s" #define UUID_UPPER "%s%s%s<%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X>%s%s%s" -/* pointer to config for global context */ -struct convert_config *global_config; +static const char *missing = ""; static int read_entry_from_ldc_file(struct ldc_entry *entry, uint32_t log_entry_address); @@ -131,6 +129,10 @@ static const char *format_uid(uint32_t uid_ptr, int use_colors, bool be, bool up if (uid_ptr < uids_dict->base_address || uid_ptr >= uids_dict->base_address + uids_dict->data_length) { str = calloc(1, strlen(BAD_PTR_STR) + 1 + 6); + if (!str) { + log_err("can't allocate memory\n"); + exit(EXIT_FAILURE); + } sprintf(str, BAD_PTR_STR, uid_ptr); } else { uid_entry = get_uuid_entry(uid_ptr); @@ -248,6 +250,8 @@ static void process_params(struct proc_ldc_entry *pe, /* check for string printing, because it leads to logger crash */ log_err("String printing is not supported\n"); pe->params[i] = (uintptr_t)log_asprintf("", raw_param); + if (!pe->params[i]) + abort(); pe->subst_mask |= 1 << i; ++i; p += 2; @@ -256,6 +260,8 @@ static void process_params(struct proc_ldc_entry *pe, /* substitute UUID entry address with formatted string pointer from heap */ pe->params[i] = (uintptr_t)asprintf_uuid(p, raw_param, use_colors, &uuid_fmt_len); + if (!pe->params[i]) + abort(); pe->subst_mask |= 1 << i; ++i; /* replace uuid formatter with %s */ @@ -267,7 +273,12 @@ static void process_params(struct proc_ldc_entry *pe, /* %pQ format specifier */ /* substitute log entry address with formatted entry text */ pe->params[i] = (uintptr_t)asprintf_entry_text(raw_param); - pe->subst_mask |= 1 << i; + + if (pe->params[i]) + pe->subst_mask |= 1 << i; + else + pe->params[i] = (uintptr_t)missing; + ++i; /* replace entry formatter with %s */ @@ -307,13 +318,13 @@ static double to_usecs(uint64_t time) } /** Justified timestamp width for printf format string */ -static unsigned int timestamp_width(unsigned int precision) +static uint8_t timestamp_width(uint8_t precision) { /* 64bits yields less than 20 digits precision. As reported by * gcc 9.3, this avoids a very long precision causing snprintf() * to truncate time_fmt */ - assert(precision >= 0 && precision < 20); + assert(precision < 20); /* * 12 digits for units is enough for 1M seconds = 11 days which * should be enough for most test runs. @@ -327,7 +338,6 @@ static inline void print_table_header(void) { FILE *out_fd = global_config->out_fd; int hide_location = global_config->hide_location; - char time_fmt[32]; char date_string[64]; const time_t epoc_secs = time(NULL); @@ -337,16 +347,14 @@ static inline void print_table_header(void) if (gettime_ret) { log_err("clock_gettime() failed: %s\n", - strerror(gettime_ret)); + strerror(errno)); exit(1); } if (global_config->time_precision >= 0) { - const unsigned int ts_width = - timestamp_width(global_config->time_precision); - snprintf(time_fmt, sizeof(time_fmt), "%%-%ds(us)%%%ds ", - ts_width, ts_width); - fprintf(out_fd, time_fmt, " TIMESTAMP", "DELTA"); + const uint8_t ts_width = timestamp_width(global_config->time_precision); + + fprintf(out_fd, "%*s(us)%*s ", -ts_width, " TIMESTAMP", ts_width, "DELTA"); } fprintf(out_fd, "%2s %-18s ", "C#", "COMPONENT"); @@ -355,11 +363,14 @@ static inline void print_table_header(void) fprintf(out_fd, "%s", "CONTENT"); if (global_config->time_precision >= 0) { + struct tm *ltime = localtime(&epoc_secs); + /* e.g.: ktime=4263.487s @ 2021-04-27 14:21:13 -0700 PDT */ fprintf(out_fd, "\tktime=%lu.%03lus", ktime.tv_sec, ktime.tv_nsec / 1000000); - if (strftime(date_string, sizeof(date_string), - "%F %X %z %Z", localtime(&epoc_secs))) + + if (ltime && strftime(date_string, sizeof(date_string), + "%F %X %z %Z", ltime)) fprintf(out_fd, " @ %s", date_string); } @@ -461,7 +472,6 @@ static void print_entry_params(const struct log_entry_header *dma_log, char ids[TRACE_MAX_IDS_STR]; float dt = to_usecs(dma_log->timestamp - last_timestamp); struct proc_ldc_entry proc_entry; - static char time_fmt[64]; int ret; if (raw_output) @@ -502,13 +512,7 @@ static void print_entry_params(const struct log_entry_header *dma_log, ids[0] = '\0'; if (raw_output) { /* "raw" means script-friendly (not all hex) */ - const char *entry_fmt = "%s%u %u %s%s%s "; - - if (time_precision >= 0) - snprintf(time_fmt, sizeof(time_fmt), "%%.%df %%.%df ", - time_precision, time_precision); - - fprintf(out_fd, entry_fmt, + fprintf(out_fd, "%s%u %u %s%s%s ", entry->header.level == use_colors ? (LOG_LEVEL_CRITICAL ? KRED : KNRM) : "", dma_log->core_id, @@ -516,24 +520,25 @@ static void print_entry_params(const struct log_entry_header *dma_log, get_component_name(entry->header.component_class, dma_log->uid), raw_output && strlen(ids) ? "-" : "", ids); + if (time_precision >= 0) - fprintf(out_fd, time_fmt, - to_usecs(dma_log->timestamp - timestamp_origin), dt); + fprintf(out_fd, "%.*f %.*f ", + time_precision, to_usecs(dma_log->timestamp - timestamp_origin), + time_precision, dt); + if (!hide_location) fprintf(out_fd, "(%s:%u) ", format_file_name(entry->file_name, raw_output), entry->header.line_idx); } else { if (time_precision >= 0) { - const unsigned int ts_width = timestamp_width(time_precision); - - snprintf(time_fmt, sizeof(time_fmt), - "%%s[%%%d.%df] (%%%d.%df)%%s ", - ts_width, time_precision, ts_width, time_precision); + const uint8_t ts_width = timestamp_width(time_precision); - fprintf(out_fd, time_fmt, + fprintf(out_fd, "%s[%*.*f] (%*.*f)%s ", use_colors ? KGRN : "", - to_usecs(dma_log->timestamp - timestamp_origin), dt, + ts_width, time_precision, + to_usecs(dma_log->timestamp - timestamp_origin), + ts_width, time_precision, dt, use_colors ? KNRM : ""); } @@ -609,7 +614,13 @@ static int read_entry_from_ldc_file(struct ldc_entry *entry, uint32_t log_entry_ entry->params = NULL; /* set file position to beginning of processed entry */ - fseek(global_config->ldc_fd, entry_offset, SEEK_SET); + ret = fseek(global_config->ldc_fd, entry_offset, SEEK_SET); + if (ret) { + log_err("Failed to seek to entry header for offset 0x%x in dictionary.\n", + entry_offset); + ret = -errno; + goto out; + } /* fetching elf header params */ ret = fread(&entry->header, sizeof(entry->header), 1, global_config->ldc_fd); @@ -625,7 +636,7 @@ static int read_entry_from_ldc_file(struct ldc_entry *entry, uint32_t log_entry_ ret = -EINVAL; goto out; } - entry->file_name = (char *)malloc(entry->header.file_name_len); + entry->file_name = (char *)malloc(entry->header.file_name_len + 1); if (!entry->file_name) { log_err("can't allocate %d byte for entry.file_name\n", @@ -636,6 +647,8 @@ static int read_entry_from_ldc_file(struct ldc_entry *entry, uint32_t log_entry_ ret = fread(entry->file_name, sizeof(char), entry->header.file_name_len, global_config->ldc_fd); + entry->file_name[entry->header.file_name_len] = '\0'; + if (ret != entry->header.file_name_len) { log_err("Failed to read source filename for offset 0x%x in dictionary.\n", entry_offset); @@ -649,7 +662,7 @@ static int read_entry_from_ldc_file(struct ldc_entry *entry, uint32_t log_entry_ ret = -EINVAL; goto out; } - entry->text = (char *)malloc(entry->header.text_len); + entry->text = (char *)malloc(entry->header.text_len + 1); if (!entry->text) { log_err("can't allocate %d byte for entry.text\n", entry->header.text_len); ret = -ENOMEM; @@ -662,6 +675,7 @@ static int read_entry_from_ldc_file(struct ldc_entry *entry, uint32_t log_entry_ ret = -1; goto out; } + entry->text[entry->header.text_len] = '\0'; return 0; @@ -900,8 +914,13 @@ static int logger_read(void) /* When the address is not correct, move forward by one DWORD (not * entire struct dma_log) */ - fseek(global_config->in_fd, -(sizeof(dma_log) - sizeof(uint32_t)), - SEEK_CUR); + ret = fseek(global_config->in_fd, -(sizeof(dma_log) - sizeof(uint32_t)), + SEEK_CUR); + if (ret) { + log_err("fetch_entry() failed on seek, aborting\n"); + ret = -errno; + break; + } skipped_dwords++; continue; @@ -998,7 +1017,7 @@ static int dump_ldc_info(void) if (global_config->version_fd) { struct sof_ipc_fw_version ver; - if (fread(&ver, sizeof(ver), 1, global_config->version_fd)) + if (fread(&ver, sizeof(ver), 1, global_config->version_fd) == 1) fprintf(out_fd, "Loaded FW expects checksum\t0x%08x\n", ver.src_hash); } @@ -1017,7 +1036,7 @@ static int dump_ldc_info(void) while (remaining > 0) { name = format_uid_raw(&uid_ptr[cnt], 0, 0, false, false); uid_addr = get_uuid_key(&uid_ptr[cnt]); - fprintf(out_fd, "\t%p %s\n", (void *)uid_addr, name); + fprintf(out_fd, "\t%p %s\n", (void *)uid_addr, name ? name : missing); if (name) { free(name); @@ -1033,36 +1052,45 @@ static int dump_ldc_info(void) return 0; } -int convert(struct convert_config *config) +int convert(void) { - struct snd_sof_logs_header snd; + struct snd_sof_logs_header * const logs_hdr = malloc(sizeof(*logs_hdr)); struct snd_sof_uids_header uids_hdr; int count, ret = 0; - config->logs_header = &snd; - global_config = config; + /* const pointer initialized at build time */ + if (!global_config) + abort(); + + if (!logs_hdr) + abort(); + + /* just a shorter alias */ + struct convert_config * const config = global_config; - count = fread(&snd, sizeof(snd), 1, config->ldc_fd); + config->logs_header = logs_hdr; + + count = fread(logs_hdr, sizeof(*logs_hdr), 1, config->ldc_fd); if (!count) { log_err("Error while reading %s.\n", config->ldc_file); return -ferror(config->ldc_fd); } - if (strncmp((char *) snd.sig, SND_SOF_LOGS_SIG, SND_SOF_LOGS_SIG_SIZE)) { + if (strncmp((char *)logs_hdr->sig, SND_SOF_LOGS_SIG, SND_SOF_LOGS_SIG_SIZE)) { log_err("Invalid ldc file signature.\n"); return -EINVAL; } if (global_config->version_fw && /* -n option */ !global_config->dump_ldc) { - ret = verify_ldc_checksum(global_config->logs_header->version.src_hash); + ret = verify_ldc_checksum(logs_hdr->version.src_hash); if (ret) return ret; } /* default logger and ldc_file abi verification */ if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_DBG_VERSION, - snd.version.abi_version)) { + logs_hdr->version.abi_version)) { log_err("abi version in %s file does not coincide with abi version used by logger.\n", config->ldc_file); log_err("logger ABI Version is %d:%d:%d\n", @@ -1070,14 +1098,19 @@ int convert(struct convert_config *config) SOF_ABI_VERSION_MINOR(SOF_ABI_DBG_VERSION), SOF_ABI_VERSION_PATCH(SOF_ABI_DBG_VERSION)); log_err("ldc_file ABI Version is %d:%d:%d\n", - SOF_ABI_VERSION_MAJOR(snd.version.abi_version), - SOF_ABI_VERSION_MINOR(snd.version.abi_version), - SOF_ABI_VERSION_PATCH(snd.version.abi_version)); + SOF_ABI_VERSION_MAJOR(logs_hdr->version.abi_version), + SOF_ABI_VERSION_MINOR(logs_hdr->version.abi_version), + SOF_ABI_VERSION_PATCH(logs_hdr->version.abi_version)); return -EINVAL; } /* read uuid section header */ - fseek(config->ldc_fd, snd.data_offset + snd.data_length, SEEK_SET); + ret = fseek(config->ldc_fd, logs_hdr->data_offset + logs_hdr->data_length, SEEK_SET); + if (ret) { + log_err("Error while seeking to uuids header from %s.\n", config->ldc_file); + return -errno; + } + count = fread(&uids_hdr, sizeof(uids_hdr), 1, config->ldc_fd); if (!count) { log_err("Error while reading uuids header from %s.\n", config->ldc_file); diff --git a/tools/logger/convert.h b/tools/logger/convert.h index 607e5e59e049..a870c990b9e4 100644 --- a/tools/logger/convert.h +++ b/tools/logger/convert.h @@ -41,10 +41,14 @@ struct convert_config { int dump_ldc; int hide_location; int relative_timestamps; - int time_precision; + int8_t time_precision; struct snd_sof_uids_header *uids_dict; struct snd_sof_logs_header *logs_header; }; uint32_t get_uuid_key(const struct sof_uuid_entry *entry); -int convert(struct convert_config *config); + +/* pointer to config for global context */ +extern struct convert_config * const global_config; + +int convert(void); diff --git a/tools/logger/filter.c b/tools/logger/filter.c index b70af55f156b..0943f68d16b2 100644 --- a/tools/logger/filter.c +++ b/tools/logger/filter.c @@ -22,8 +22,6 @@ #define COMPONENTS_SEPARATOR ',' #define COMPONENT_NAME_SCAN_STRING_LENGTH 32 -extern struct convert_config *global_config; - /** map between log level given by user and enum value */ static const struct { const char name[16]; @@ -137,7 +135,7 @@ static char *filter_parse_component_name(char *input_str, struct filter_element * `*` * Whitespace is possible at the begin, end and after `name`. * `name` must refer to values from given UUID dictionary, - * (so name comes from DECLARE_SOF_UUID macro usage) + * (so name comes from SOF_DEFINE_UUID macro usage) * @param input_str formatted component definition * @param out element where component definition should be saved diff --git a/tools/logger/logger.c b/tools/logger/logger.c index 543e8e85a0c8..b6c2b26bd629 100644 --- a/tools/logger/logger.c +++ b/tools/logger/logger.c @@ -5,6 +5,7 @@ // Author: Bartosz Kokoszko // Artur Kloniecki +#include #include #include #include @@ -15,6 +16,18 @@ #include #include #include + +#include +#include + +#include "config.h" + +#if HAS_INOTIFY +#include +#include +#include +#endif + #include "convert.h" #include "misc.h" @@ -91,8 +104,8 @@ static int snapshot(const char *name) for (i = 0; i < ARRAY_SIZE(debugfs); i++) { - sprintf(pinname, "%s/%s", path, debugfs[i]); - sprintf(poutname, "%s.%s.txt", name, debugfs[i]); + snprintf(pinname, sizeof(pinname), "%s/%s", path, debugfs[i]); + snprintf(poutname, sizeof(poutname), "%s.%s.txt", name, debugfs[i]); /* open debugfs for reading */ in_fd = fopen(pinname, "rb"); @@ -119,9 +132,14 @@ static int snapshot(const char *name) if (count != 4) break; - sprintf(buffer, "0x%6.6x: 0x%8.8x\n", addr, val); + snprintf(buffer, sizeof(buffer), "0x%6.6x: 0x%8.8x\n", addr, val); - count = fwrite(buffer, 1, strlen(buffer), out_fd); + i = strlen(buffer); + count = fwrite(buffer, 1, i, out_fd); + if (count != i) { + fprintf(stderr, "error: an error occurred during write to %s: %s\n", + poutname, strerror(errno)); + } addr += 4; } @@ -151,7 +169,12 @@ static int configure_uart(const char *file, unsigned int baud) tio.c_cc[VMIN] = 1; ret = tcsetattr(fd, TCSANOW, &tio); - return ret < 0 ? -errno : fd; + if (ret < 0) { + close(fd); + return -errno; + } + + return fd; } /* Concantenate `config->filter_config` with `input` + `\n` */ @@ -170,6 +193,108 @@ static int append_filter_config(struct convert_config *config, const char *input return 0; } +#if !HAS_INOTIFY +static void *wait_open(const char *watched_dir, const char *expected_file) +{ + assert(HAS_INOTIFY); /* Should never be called */ +} +#else +/* + * This 5 minutes timeout is for "backward compatible safety" in case + * any user/script of an earlier, pre-inotify version of sof-logger + * (accidentally?) _expected_ the tool to quit when the driver was not + * loaded. This expectation was always wrong but an infinite wait is too + * severe of a punishment. This timeout cannot be too small otherwise it + * would defeat the new feature. + */ +#ifndef SOF_LOGGER_WAIT_OPEN_TIMEOUT_SECS +#define SOF_LOGGER_WAIT_OPEN_TIMEOUT_SECS (5 * 60) +#endif + +/* + * Using inotify, wait up to 5 minutes for 'expected_name' to appear in + * 'watched_dir'. Then return opendir/fopen(expected_name). fopen() and + * opendir() failures are NOT handled; e.g. NULL from fopen() is just + * passed through. + * + * Returns a FILE * or DIR * + */ +static void *wait_open(const char *watched_dir, const char *expected_file) +{ + if (access(watched_dir, R_OK)) { + fprintf(stderr, "error: %s() cannot read %s\n", __func__, watched_dir); + return NULL; + } + + const int iqueue = inotify_init(); + const int dwatch = inotify_add_watch(iqueue, watched_dir, IN_CREATE); + struct stat expected_stat; + void *ret_stream = NULL; + const int fpath_len = strlen(watched_dir) + 1 + strlen(expected_file) + 1; + char * const fpath = malloc(fpath_len); + + if (!fpath) { + fprintf(stderr, "error: can't allocate memory\n"); + exit(EXIT_FAILURE); + } + + snprintf(fpath, fpath_len, "%s/%s", watched_dir, expected_file); + + /* Not racy because the inotify watch was set first. */ + if (!access(fpath, F_OK)) + goto fopenit; + + fprintf(stdout, "%s: waiting in %s/ for %s\n", APP_NAME, watched_dir, expected_file); + fflush(stdout); + + while (1) { + char evlist[1 * sizeof(struct inotify_event)]; + struct pollfd pfd[1] = {{ .fd = iqueue, .events = POLLIN, .revents = 0 }}; + int pret = poll(pfd, sizeof(pfd) / sizeof(struct pollfd), + SOF_LOGGER_WAIT_OPEN_TIMEOUT_SECS * 1000); + + if (pret == 0) { + fprintf(stderr, "error: no %s after waiting %d seconds\n", + expected_file, SOF_LOGGER_WAIT_OPEN_TIMEOUT_SECS); + goto cleanup; + } + + if (pret < 0 || pfd[0].revents != POLLIN) { + fprintf(stderr, "error: in %s, poll(%s) returned %d, %d\n", + __func__, watched_dir, pret, pfd[0].revents); + goto cleanup; + } + + /* Silence __attribute__((warn_unused_result)) which is + * enabled by some Linux distros even when broken in gcc: + * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66425 + */ + (void)!read(iqueue, evlist, sizeof(evlist)); + + if (!access(fpath, F_OK)) + goto fopenit; + } + +fopenit: + if (stat(fpath, &expected_stat)) + goto cleanup; + + if ((expected_stat.st_mode & S_IFMT) == S_IFDIR) + ret_stream = opendir(fpath); + else + ret_stream = fopen(fpath, "rb"); + +cleanup: + free(fpath); + inotify_rm_watch(iqueue, dwatch); + + return ret_stream; +} +#endif // HAS_INOTIFY + +static struct convert_config _global_config; +struct convert_config * const global_config = &_global_config; + int main(int argc, char *argv[]) { static const char optstring[] = "ho:i:l:ps:c:u:tv:rd:Le:f:gF:n"; @@ -249,7 +374,8 @@ int main(int argc, char *argv[]) if (i < 0 || 1 < i) { fprintf(stderr, "%s: invalid option: -e %s\n", APP_NAME, optarg); - return -EINVAL; + ret = -EINVAL; + goto out; } config.relative_timestamps = i; break; @@ -258,7 +384,8 @@ int main(int argc, char *argv[]) config.time_precision = atoi(optarg); if (config.time_precision < 0) { usage(); - return -EINVAL; + ret = -EINVAL; + goto out; } break; case 'g': @@ -288,8 +415,10 @@ int main(int argc, char *argv[]) usage(); } - if (snapshot_file) - return baud ? EINVAL : -snapshot(snapshot_file); + if (snapshot_file) { + ret = baud ? EINVAL : -snapshot(snapshot_file); + goto out; + } if (!config.ldc_file) { fprintf(stderr, "error: Missing ldc file\n"); @@ -304,17 +433,6 @@ int main(int argc, char *argv[]) goto out; } - if (config.version_fw) { - config.version_fd = fopen(config.version_file, "rb"); - if (!config.version_fd && !config.dump_ldc) { - ret = errno; - fprintf(stderr, - "error: Unable to open version file %s: %s\n", - config.version_file, strerror(ret)); - goto out; - } - } - if (config.out_file) { config.out_fd = fopen(config.out_file, "w"); if (!config.out_fd) { @@ -339,6 +457,11 @@ int main(int argc, char *argv[]) if (!config.in_file && !config.dump_ldc) config.in_file = "/sys/kernel/debug/sof/etrace"; + if (!config.in_file && baud) { + fprintf(stderr, "error: No UART device specified\n"); + usage(); + } + if (config.input_std) { config.in_fd = stdin; } else if (baud) { @@ -348,7 +471,27 @@ int main(int argc, char *argv[]) goto out; } } else if (config.in_file) { - config.in_fd = fopen(config.in_file, "rb"); + static const char sys_debug[] = "/sys/kernel/debug"; + static const char sys_debug_sof[] = "/sys/kernel/debug/sof"; + /* In the future we could add an option to force waiting + * for _any_ input file, not just for /sys/kernel/debug/sof/[e]trace + */ + config.in_fd = NULL; + if (!HAS_INOTIFY || + strncmp(config.in_file, sys_debug, strlen(sys_debug))) { + config.in_fd = fopen(config.in_file, "rb"); + } else { // both inotify and /sys/kernel/debug/ + DIR *dbg_sof = (DIR *)wait_open(sys_debug, "sof"); + + if (dbg_sof) { + closedir(dbg_sof); + config.in_fd = + (FILE *)wait_open(sys_debug_sof, + config.in_file + + strlen(sys_debug_sof) + 1); + } + } + if (!config.in_fd) { ret = errno; fprintf(stderr, @@ -360,7 +503,19 @@ int main(int argc, char *argv[]) if (isatty(fileno(config.out_fd)) != 1) config.use_colors = 0; - ret = -convert(&config); + if (config.version_fw) { + config.version_fd = fopen(config.version_file, "rb"); + if (!config.version_fd && !config.dump_ldc) { + ret = errno; + fprintf(stderr, + "error: Unable to open version file %s: %s\n", + config.version_file, strerror(ret)); + goto out; + } + } + + _global_config = config; + ret = -convert(); out: /* free memory */ diff --git a/tools/logger/misc.c b/tools/logger/misc.c index 37ca30adb9c1..20e5db9f95b8 100644 --- a/tools/logger/misc.c +++ b/tools/logger/misc.c @@ -41,8 +41,6 @@ char *log_asprintf(const char *format, ...) return result; } -extern struct convert_config *global_config; - /** Prints 1. once to stderr. 2. a second time to the global out_fd if * out_fd is neither stderr nor stdout (because the -o option was used). */ diff --git a/tools/mtrace/mtrace-reader.c b/tools/mtrace/mtrace-reader.c new file mode 100644 index 000000000000..ae5eb31d68a8 --- /dev/null +++ b/tools/mtrace/mtrace-reader.c @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2025 Intel Corporation. +// +// Author: Kai Vehmanen +// +// Compile instructions: +// gcc -o mtrace-reader mtrace-reader.c -Wall -O2 +// + +#include +#include +#include +#include +#include +#include +#include + +#define READ_BUFFER 16384 +#define MTRACE_FILE "/sys/kernel/debug/sof/mtrace/core0" + +uint8_t buffer[READ_BUFFER]; + +int main(void) +{ + ssize_t read_bytes; + uint32_t data_len; + uint32_t header; + int fd; + + fd = open(MTRACE_FILE, O_RDONLY); + if (fd < 0) { + perror("open"); + return 1; + } + + while (1) { + read_bytes = read(fd, buffer, READ_BUFFER); + + /* handle end-of-file */ + if (read_bytes == 0) + continue; + + if (read_bytes <= 4) + continue; + + header = *(uint32_t *)buffer; + data_len = header; + if (data_len > read_bytes - 4) + continue; + + if (write(STDOUT_FILENO, buffer + 4, data_len) < 0) { + perror("write"); + return -1; + } + } + + close(fd); + + return 0; +} diff --git a/tools/mtrace/mtrace-reader.py b/tools/mtrace/mtrace-reader.py new file mode 100644 index 000000000000..d0acbd1cfce6 --- /dev/null +++ b/tools/mtrace/mtrace-reader.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: BSD-3-Clause +# +# Copyright (c) 2022, Intel Corporation. All rights reserved. + +#pylint:disable=mixed-indentation + +# Tool to stream data from Linux SOF driver "mtrace" debugfs +# interface to standard output. Plain "cat" is not sufficient +# as each read() syscall returns log data with a 32bit binary +# header, containing the payload length. + +import struct +import os +import sys +import argparse + +READ_BUFFER = 16384 +MTRACE_FILE = "/sys/kernel/debug/sof/mtrace/core0" + +parser = argparse.ArgumentParser() +parser.add_argument('-m', '--mark-chunks', + action='store_true') + +args = parser.parse_args() + +chunk_idx = 0 + +fd = os.open(MTRACE_FILE, os.O_RDONLY) +while fd >= 0: + # direct unbuffered os.read() must be used to comply with + # debugfs protocol used. each non-zero read will return + # a buffer containing a 32bit header and a payload + read_bytes = os.read(fd, READ_BUFFER) + + # handle end-of-file + if len(read_bytes) == 0: + continue + + if len(read_bytes) <= 4: + continue + + header = struct.unpack('I', read_bytes[0:4]) + data_len = header[0] + data = read_bytes[4:4+data_len] + + if (args.mark_chunks): + chunk_msg = "\n--- Chunk #{} start (size: {}) ---\n" .format(chunk_idx, data_len) + sys.stdout.write(chunk_msg) + + sys.stdout.buffer.write(data) + sys.stdout.flush() + chunk_idx += 1 diff --git a/tools/plugin/CMakeLists.txt b/tools/plugin/CMakeLists.txt new file mode 100644 index 000000000000..a137a4b7d04c --- /dev/null +++ b/tools/plugin/CMakeLists.txt @@ -0,0 +1,72 @@ +# SPDX-License-Identifier: BSD-3-Clause + +cmake_minimum_required(VERSION 3.13) + +project(SOF_PLUGIN C CXX) + +include(../../scripts/cmake/misc.cmake) +include(CheckCCompilerFlag) + +set(sof_source_directory "${PROJECT_SOURCE_DIR}/../..") +set(sof_install_directory "${PROJECT_BINARY_DIR}/sof_ep/install") +set(sof_binary_directory "${PROJECT_BINARY_DIR}/sof_ep/build") + +set(config_h ${sof_binary_directory}/library_autoconfig.h) + +set(parser_source_directory "${PROJECT_SOURCE_DIR}/../tplg_parser") +set(parser_install_dir "${PROJECT_BINARY_DIR}/sof_parser/install") + +message("ipc4 build") +set(tplg_ipc plugin_ipc4_defconfig) + +include(ExternalProject) + +# External project for SOF library +ExternalProject_Add(sof_ep + DOWNLOAD_COMMAND "" + SOURCE_DIR "${sof_source_directory}" + PREFIX "${PROJECT_BINARY_DIR}/sof_ep" + BINARY_DIR "${sof_binary_directory}" + CMAKE_ARGS -DCONFIG_LIBRARY=ON + -DCMAKE_INSTALL_PREFIX=${sof_install_directory} + -DCMAKE_VERBOSE_MAKEFILE=${CMAKE_VERBOSE_MAKEFILE} + -DINIT_CONFIG=${tplg_ipc} + -DCONFIG_H_PATH=${config_h} + -DCONFIG_LIBRARY_STATIC=ON + -DOPTIMIZE_FOR_DEBUG=ON + BUILD_ALWAYS 1 + BUILD_BYPRODUCTS "${sof_install_directory}/lib/libsof.a" +) + +add_library(sof_library STATIC IMPORTED) +set_target_properties(sof_library PROPERTIES IMPORTED_LOCATION "${sof_install_directory}/lib/libsof.a") +add_dependencies(sof_library sof_ep) + +# External project for topology parser +ExternalProject_Add(parser_ep + SOURCE_DIR "${parser_source_directory}" + PREFIX "${PROJECT_BINARY_DIR}/sof_parser" + BINARY_DIR "${PROJECT_BINARY_DIR}/sof_parser/build" + CMAKE_ARGS -DCONFIG_LIBRARY=ON + -DCMAKE_INSTALL_PREFIX=${PROJECT_BINARY_DIR}/sof_parser/install + -DCMAKE_VERBOSE_MAKEFILE=${CMAKE_VERBOSE_MAKEFILE} + -DCONFIG_LIBRARY_STATIC=ON + -DOPTIMIZE_FOR_DEBUG=ON + BUILD_ALWAYS 1 + BUILD_BYPRODUCTS "${parser_install_dir}/lib/libsof_tplg_parser.a" +) + +add_library(sof_parser_lib STATIC IMPORTED) +set_target_properties(sof_parser_lib PROPERTIES IMPORTED_LOCATION "${parser_install_dir}/lib/libsof_tplg_parser.a") +add_dependencies(sof_parser_lib parser_ep) + +add_subdirectory(alsaconf) +add_subdirectory(alsaplug) +add_subdirectory(modules) +add_subdirectory(pipe) + +add_dependencies(asound_module_pcm_sof sof_parser_lib) +add_dependencies(asound_module_ctl_sof sof_parser_lib) + +add_dependencies(sof-pipe sof_mod_shm) +add_dependencies(sof-pipe sof_mod_alsa) diff --git a/tools/plugin/README.md b/tools/plugin/README.md new file mode 100644 index 000000000000..e225437b404f --- /dev/null +++ b/tools/plugin/README.md @@ -0,0 +1,161 @@ +# ALSA Plugin + +The SOF ALSA plugin allows SOF topologies to be run on the host. The plugin +is still WIP with many rough edges that need refined before production +deployment, however the plugin is usable today as a rapid development +framework for SOF infrastructure and processing. + +### Features + * aplay & arecord usage working today + * alsamixer & amixer usage not working today + * modules are loaded as SO shared libraries. + * topology is parsed by the plugin and pipelines associated with the requested PCM ID are loaded + * pipelines run as individual userspace threads + * pipelines can be pinned to efficency cores + * pipelines can use realtime priority. + * alsa sink and alsa source modules available. + * pipelines can block (non blocking and mmap todo) + +### License +Code is a mixture of LGPL and BSD 3c. + +### Usage +Please build as cmake project, for IPC4 (functional) +IPC3 is not functional and not supported + +``` +cd sof +cmake -GNinja -B build-plugin/ -S tools/plugin/ +# Build external projects first to avoid build race condition +# Dropping -GNinja is another (very slow) option. +cmake --build build-plugin/ -- sof_ep parser_ep +cmake --build build-plugin/ +``` +then (use default ALSA prefix atm) + +``` +sudo cmake --install build-plugin/ + +Make sure to set the LD_LIBRARY_PATH to include directory where the SOF modules are installed +Ex: "~/work/sof/sof/build-plugin/sof_ep/install/lib:~/work/sof/sof/build-plugin/modules/" +And set the environment variable SOF_PLUGIN_TOPOLOGY_PATH to point to the directory containing the topology binary +``` + +Code can then be run by starting sof-pipe with your desired topology (Ex: sof-plugin.tplg) + +``` + ./sof-pipe -T sof-plugin.tplg +``` + +At this point the sof-pipe daemon is waiting for IPC. Audio applications can now invoke sof-pipe processing via + +``` +aplay -Dsof:plugin:1:default:default:48k2c16b -f dat some48kHz.wav +``` +The command line is parsed as follows: +- "sof" is the plugin name +- "sof-plugin" is the topology name. The "sof-" prefix and ".tplg" suffix will be appended by the plugin +- "1" is the PCM ID for render/capture +- "default": The first default is the card name +- "default": The second default is the device name +- "48k2c16b" is the config name for 48K, stereo, 16bit + +Config name is optional in the command line. When it is not provided, hw_params will be used to +configure the endpoint. In this case, the command line can be simplified to: + +``` +aplay -Dsof:plugin:1:default:default +``` + +When using the default device, the command line can be further simplified to: + +``` +aplay -Dsof:plugin:1 +``` + +This renders audio to the sof-pipe daemon using the sof-plugin topology playback PCM ID 1. +The above example needs to be 48k as example pipe has no SRC/ASRC. + +Likewise + +``` +arecord -Dsof:plugin:1:default:default:48k2c16b -f dat file.wav +``` +Will record audio using the plugin topology and PCM ID 1. + +Mixer settings can be adjusted for sof-plugin by + +``` +alsamixer -Dsof:plugin +``` +or +``` +amixer -Dsof:plugin cset numid=1 20 +``` +Bytes control data can be set using sof-ctl as follows: + +``` +./sof-ctl -Dsof:plugin -n 4 -r -i 4 -p 2 -s ~/data.txt +``` +where -n is the numid of the kcontrol, -i is the IPC version, -p is the param ID and -s specifies +the data in csv format. + +Bytes control data can be read using sof-ctl as follows: +``` +./sof-ctl -Dsof:plugin -n 4 -i 4 -p 2 +``` +where -n is the numid of the kcontrol, -i is the IPC version and -p is the param ID. + +Note: Bytes controls must have tlv_read/tlv_write and tlv_callback access. + +## Instructions for testing OpenVino noise suppression model with the SOF plugin: +1. Prepare Open Model Zoo repository: + + - Clone repo: + ``` + git clone --recurse-submodules https://github.com/openvinotoolkit/open_model_zoo.git + ``` + + - Build demo applications: + https://docs.openvino.ai/2023.3/omz_demos.html#a-name-build-demos-linux-a-build-the-demo-applications-on-linux + +2. Source OpenVino environment and get OpenCV +https://www.intel.com/content/www/us/en/developer/tools/openvino-toolkit-download.html + +3. Worth building and running the demo noise suppression application in the open model zoo +repository to make sure OpenVino and OpenCV are configured properly. +Fetch the model, e.g., `noise-suppression-poconetlike-0001.xml`, by running this demo: + + https://docs.openvino.ai/2023.3/omz_demos_noise_suppression_demo_cpp.html + +4. Set environment variable NOISE_SUPPRESSION_MODEL_NAME to point to the model file +``` +export NOISE_SUPPRESSION_MODEL_NAME=~/open_model_zoo/demos/noise_suppression_demo/cpp/intel/noise-suppression-poconetlike-0001/FP16/noise-suppression-poconetlike-0001.xml + +``` +5. Set environment variable OpenVINO_DIR to include CMake files (e.g., OpenVINOConfig.cmake) for building the plugin: +``` +export OpenVINO_DIR=~/openvino_env/lib/python3.10/site-packages/openvino/cmake + +``` +6. Update environment variable LD_LIBRARY_PATH to include necessary directories for the plugin: + +``` +export LD_LIBRARY_PATH=~/work/sof/sof/build-plugin/sof_ep/install/lib:~/work/sof/sof/build-plugin/modules/:~/work/sof/sof/build-plugin/modules/ov_noise_suppression/ +``` + +7. Enable noise suppression by setting NOISE_SUPPRESSION=true in the tplg-targets for the sof-plugin topology + +8. Rebuild the plugin: Remove the previous build-build directory and rebuild it by following the usage steps above. + +9. Start capture using the following command. This uses the 16K capture from the DMIC from +PCM hw device 0,7. Currently, only 16K capture is supported but in the future this will be expanded +to work with 48K capture. +``` +arecord -Dsof:plugin:1:0:7:16k2c16b -f dat -r 16000 --period-size=2048 file_ns_16k.wav +``` + +### TODO Items for single pipeline E2E audio +Use hw_params instead of hardcoded config +Add support for 24-bit and 16-bit audio +Remove duplication of kcontrols when alsamixer is opened/closed repeatedly \ No newline at end of file diff --git a/tools/plugin/alsaconf/50-sof.conf b/tools/plugin/alsaconf/50-sof.conf new file mode 100644 index 000000000000..91566ec28a55 --- /dev/null +++ b/tools/plugin/alsaconf/50-sof.conf @@ -0,0 +1,94 @@ +# Invoke a SOF pipeline and route it to a sound card or another plugin +# To use the PCM 0 on the SOF "volume" topology and route it to hw:1,2 then run +# aplay -Dsof:volume:0,hw,1,2 +# tplg:pipe:[pipe:card:dev:config[pipe:card:dev:config]...] +# +# TPLG SOF topology configuration (mandatory) + +pcm.sof { + @args [ TPLG ] + @args.TPLG { + type string + default "passthrough" + } + + type sof + tplg $TPLG + + hint { + show { + @func refer + name defaults.namehint.basic + } + description "SOF Topology and PCM" + } + + config.48k2c16b { + rate 48000 + channels 2 + format S16_LE + period_time 0 + period_frames 48 + buffer_time 0 + buffer_frames 192 + } + + config.16k2c16b { + rate 16000 + channels 2 + format S16_LE + period_time 0 + period_frames 2048 # based on the period size needed for Noise suppression module + buffer_time 0 + buffer_frames 8192 + } + + config.48k8c16b { + rate 48000 + channels 8 + format S16_LE + period_time 0 + period_frames 1024 + buffer_time 0 + buffer_frames 5120 + } + + config.8k8c16b { + rate 8000 + channels 8 + format S16_LE + period_time 0 + period_frames 1024 + buffer_time 0 + buffer_frames 5120 + } + + config.48k2c32b { + rate 48000 + channels 2 + format S32_LE + period_time 0 + period_frames 6000 + buffer_time 0 + buffer_frames 24000 + } +} + +ctl.sof { + @args [ TPLG ] + @args.TPLG { + type string + default "passthrough" + } + + type sof + tplg $TPLG + + hint { + show { + @func refer + name defaults.namehint.basic + } + description "SOF Topology" + } +} diff --git a/tools/plugin/alsaconf/CMakeLists.txt b/tools/plugin/alsaconf/CMakeLists.txt new file mode 100644 index 000000000000..3a58c514f522 --- /dev/null +++ b/tools/plugin/alsaconf/CMakeLists.txt @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: BSD-3-Clause + +install(FILES 50-sof.conf DESTINATION /usr/share/alsa/alsa.conf.d) +# HACK needs to link to above +install(FILES 50-sof.conf DESTINATION /etc/alsa/conf.d) diff --git a/tools/plugin/alsaplug/CMakeLists.txt b/tools/plugin/alsaplug/CMakeLists.txt new file mode 100644 index 000000000000..d60104d8245d --- /dev/null +++ b/tools/plugin/alsaplug/CMakeLists.txt @@ -0,0 +1,69 @@ +# SPDX-License-Identifier: BSD-3-Clause + +# PCM ALSA module +add_library(asound_module_pcm_sof MODULE + pcm.c + plugin.c + ../common.c + tplg.c + tplg_ctl.c +) + +sof_append_relative_path_definitions(asound_module_pcm_sof) + +target_include_directories(asound_module_pcm_sof PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/.. + ${sof_source_directory}/src/audio) + +target_compile_options(asound_module_pcm_sof PRIVATE -DPIC -g -O3 -Wmissing-prototypes + -Wno-stringop-truncation -Wimplicit-fallthrough -DCONFIG_LIBRARY -imacros${config_h}) + +install(TARGETS asound_module_pcm_sof DESTINATION /usr/lib/x86_64-linux-gnu/alsa-lib) + +target_link_options(asound_module_pcm_sof PRIVATE -Wl,--export-dynamic,--no-undefined) +target_link_libraries(asound_module_pcm_sof PRIVATE sof_library) +target_link_libraries(asound_module_pcm_sof PRIVATE sof_parser_lib) +target_link_libraries(asound_module_pcm_sof PRIVATE pthread) +target_link_libraries(asound_module_pcm_sof PRIVATE -lasound -ldl -lm -lasound -lrt) + +target_include_directories(asound_module_pcm_sof PRIVATE ${sof_install_directory}/include) +target_include_directories(asound_module_pcm_sof PRIVATE ${parser_install_dir}/include) + +set_target_properties(asound_module_pcm_sof + PROPERTIES + INSTALL_RPATH "${sof_install_directory}/alsa-lib" + INSTALL_RPATH_USE_LINK_PATH TRUE +) + +# CTL ALSA module +add_library(asound_module_ctl_sof MODULE + ctl.c + plugin.c + ../common.c +) +sof_append_relative_path_definitions(asound_module_ctl_sof) +target_include_directories(asound_module_ctl_sof PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/.. + ${sof_source_directory}/src/audio) + +target_compile_options(asound_module_ctl_sof PRIVATE -DPIC -g -O3 -Wmissing-prototypes + -Wimplicit-fallthrough -Wno-stringop-truncation -Wall -Werror -DCONFIG_LIBRARY -imacros${config_h}) + +install(TARGETS asound_module_ctl_sof DESTINATION /usr/lib/x86_64-linux-gnu/alsa-lib) + +target_link_options(asound_module_ctl_sof PRIVATE -Wl,--export-dynamic,--no-undefined) +target_link_libraries(asound_module_ctl_sof PRIVATE sof_library) +target_link_libraries(asound_module_ctl_sof PRIVATE sof_parser_lib) +target_link_libraries(asound_module_ctl_sof PRIVATE pthread) +target_link_libraries(asound_module_ctl_sof PRIVATE -lasound -ldl -lm -lasound -lrt) + +target_include_directories(asound_module_ctl_sof PRIVATE ${sof_install_directory}/include) +target_include_directories(asound_module_ctl_sof PRIVATE ${parser_install_dir}/include) + +set_target_properties(asound_module_ctl_sof + PROPERTIES + INSTALL_RPATH "${sof_install_directory}/alsa-lib" + INSTALL_RPATH_USE_LINK_PATH TRUE +) diff --git a/tools/plugin/alsaplug/conf.c b/tools/plugin/alsaplug/conf.c new file mode 100644 index 000000000000..5b259bf6885a --- /dev/null +++ b/tools/plugin/alsaplug/conf.c @@ -0,0 +1,47 @@ +/*-*- linux-c -*-*/ + +/* + * ALSA <-> SOF Config plugin + * + * Copyright(c) 2022 Intel Corporation. All rights reserved. + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + * + */ + +#include +#include +#include "plugin.h" + +/* Not actually part of the alsa api.... */ +extern int +snd_config_hook_load(snd_config_t *root, snd_config_t *config, + snd_config_t **dst, snd_config_t *private_data); + +/* manifest file could be loaded as a hook */ +int sofplug_load_hook(snd_config_t *root, snd_config_t *config, + snd_config_t **dst, snd_config_t *private_data) +{ + int ret = 0; + + *dst = NULL; + + /* TODO: load hook when SOF ready */ + return snd_config_hook_load(root, config, dst, private_data); +} + +SND_DLSYM_BUILD_VERSION(sofplug_load_hook, + SND_CONFIG_DLSYM_VERSION_HOOK); diff --git a/tools/plugin/alsaplug/ctl.c b/tools/plugin/alsaplug/ctl.c new file mode 100644 index 000000000000..85a31177cfca --- /dev/null +++ b/tools/plugin/alsaplug/ctl.c @@ -0,0 +1,845 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2022 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// TODO remove parsing and read ctls from sof-pipe SHM glb context +#include + +#include +#include + +#include "plugin.h" +#include "common.h" + +typedef struct snd_sof_ctl { + struct plug_shm_glb_state *glb; + snd_ctl_ext_t ext; + struct plug_socket_desc ipc; + struct plug_shm_desc shm_ctx; + int subscribed; + int updated[MAX_CTLS]; + +} snd_sof_ctl_t; + +#define CTL_GET_TPLG_HDR(_ctl, _key) \ + (&_ctl->glb->ctl[_key].mixer_ctl.hdr) + +#define CTL_GET_TPLG_MIXER(_ctl, _key) \ + (&_ctl->glb->ctl[_key].mixer_ctl) + +#define CTL_GET_TPLG_ENUM(_ctl, _key) \ + (&_ctl->glb->ctl[_key].enum_ctl) + +#define CTL_GET_TPLG_BYTES(_ctl, _key) \ + (&_ctl->glb->ctl[_key].bytes_ctl) + +static uint32_t mixer_to_ipc(unsigned int value, uint32_t *volume_table, int size) +{ + if (value >= size) + return volume_table[size - 1]; + + return volume_table[value]; +} + +static uint32_t ipc_to_mixer(uint32_t value, uint32_t *volume_table, int size) +{ + int i; + + for (i = 0; i < size; i++) { + if (volume_table[i] >= value) + return i; + } + + return i - 1; +} + +/* number of ctls */ +static int plug_ctl_elem_count(snd_ctl_ext_t *ext) +{ + snd_sof_ctl_t *ctl = ext->private_data; + + /* TODO: get count of elems from topology */ + return ctl->glb->num_ctls; +} + +static int plug_ctl_elem_list(snd_ctl_ext_t *ext, unsigned int offset, snd_ctl_elem_id_t *id) +{ + snd_sof_ctl_t *ctl = ext->private_data; + struct snd_soc_tplg_ctl_hdr *hdr; + + if (offset >= ctl->glb->num_ctls) + return -EINVAL; + + hdr = CTL_GET_TPLG_HDR(ctl, offset); + + snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER); + snd_ctl_elem_id_set_name(id, hdr->name); + + return 0; +} + +static snd_ctl_ext_key_t plug_ctl_find_elem(snd_ctl_ext_t *ext, const snd_ctl_elem_id_t *id) +{ + snd_sof_ctl_t *ctl = ext->private_data; + unsigned int numid; + + numid = snd_ctl_elem_id_get_numid(id); + + if (numid > ctl->glb->num_ctls) + return SND_CTL_EXT_KEY_NOT_FOUND; + + return numid - 1; +} + +static int plug_ctl_get_attribute(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, + int *type, unsigned int *acc, unsigned int *count) +{ + snd_sof_ctl_t *ctl = ext->private_data; + struct snd_soc_tplg_ctl_hdr *hdr = CTL_GET_TPLG_HDR(ctl, key); + struct snd_soc_tplg_mixer_control *mixer_ctl; + struct snd_soc_tplg_enum_control *enum_ctl; + struct snd_soc_tplg_bytes_control *bytes_ctl; + int err = 0; + + switch (hdr->ops.info) { + case SND_SOC_TPLG_CTL_VOLSW: + case SND_SOC_TPLG_CTL_VOLSW_SX: + case SND_SOC_TPLG_CTL_VOLSW_XR_SX: + mixer_ctl = (struct snd_soc_tplg_mixer_control *)hdr; + + /* check for type - boolean should be binary values */ + if (mixer_ctl->max == 1 && mixer_ctl->min == 0) + *type = SND_CTL_ELEM_TYPE_BOOLEAN; + else + *type = SND_CTL_ELEM_TYPE_INTEGER; + *count = 2;//mixer_ctl->num_channels; ///// WRONG is 0 !!! + + //printf("mixer %d %d\n", __LINE__, mixer_ctl->num_channels); + break; + case SND_SOC_TPLG_CTL_ENUM: + case SND_SOC_TPLG_CTL_ENUM_VALUE: + enum_ctl = (struct snd_soc_tplg_enum_control *)hdr; + *type = SND_CTL_ELEM_TYPE_ENUMERATED; + *count = enum_ctl->items; + break; + case SND_SOC_TPLG_CTL_RANGE: + case SND_SOC_TPLG_CTL_STROBE: + // TODO: ?? + break; + case SND_SOC_TPLG_CTL_BYTES: + bytes_ctl = (struct snd_soc_tplg_bytes_control *)hdr; + *type = SND_CTL_ELEM_TYPE_BYTES; + *count = bytes_ctl->max; + break; + } + + *acc = hdr->access; + + /* access needs the callback to decode the data */ + if ((hdr->access & SND_CTL_EXT_ACCESS_TLV_READ) || + (hdr->access & SND_CTL_EXT_ACCESS_TLV_WRITE)) + *acc |= SND_CTL_EXT_ACCESS_TLV_CALLBACK; + return err; +} + +/* + * Integer ops + */ +static int plug_ctl_get_integer_info(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, long *imin, + long *imax, long *istep) +{ + snd_sof_ctl_t *ctl = ext->private_data; + struct snd_soc_tplg_ctl_hdr *hdr = CTL_GET_TPLG_HDR(ctl, key); + struct snd_soc_tplg_mixer_control *mixer_ctl = + CTL_GET_TPLG_MIXER(ctl, key); + int err = 0; + + switch (hdr->type) { + case SND_SOC_TPLG_CTL_VOLSW: + case SND_SOC_TPLG_CTL_VOLSW_SX: + case SND_SOC_TPLG_CTL_VOLSW_XR_SX: + /* TLV uses the fields differently */ + if ((hdr->access & SND_CTL_EXT_ACCESS_TLV_READ) || + (hdr->access & SND_CTL_EXT_ACCESS_TLV_WRITE)) { + *istep = mixer_ctl->hdr.tlv.scale.step; + *imin = (int32_t)mixer_ctl->hdr.tlv.scale.min; + *imax = mixer_ctl->max; + } else { + *istep = 1; + *imin = mixer_ctl->min; + *imax = mixer_ctl->max; + } + break; + default: + SNDERR("invalid ctl type for integer using key %d", key); + err = -EINVAL; + break; + } + + return err; +} + +static int plug_ctl_read_integer(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, long *value) +{ + snd_sof_ctl_t *ctl = ext->private_data; + struct snd_soc_tplg_mixer_control *mixer_ctl = CTL_GET_TPLG_MIXER(ctl, key); + struct ipc4_module_large_config config = {{ 0 }}; + struct ipc4_peak_volume_config *volume; + struct ipc4_module_large_config_reply *reply; + char *reply_data; + void *msg; + int reply_data_size, size; + int num_data_items; + int i, err; + + /* configure the IPC message */ + plug_ctl_ipc_message(&config, IPC4_VOLUME, sizeof(volume), ctl->glb->ctl[key].module_id, + ctl->glb->ctl[key].instance_id, SOF_IPC4_MOD_LARGE_CONFIG_GET); + + config.extension.r.final_block = 1; + config.extension.r.init_block = 1; + + size = sizeof(config); + msg = calloc(size, 1); + if (!msg) + return -ENOMEM; + + /* reply contains both the requested data and the reply status */ + reply_data_size = sizeof(*reply) + mixer_ctl->num_channels * sizeof(*volume); + reply_data = calloc(reply_data_size, 1); + if (!reply_data_size) { + free(msg); + return -ENOMEM; + } + + /* send the IPC message */ + memcpy(msg, &config, sizeof(config)); + err = plug_ipc_cmd_tx_rx(&ctl->ipc, msg, size, reply_data, reply_data_size); + free(msg); + if (err < 0) { + SNDERR("failed to set volume for control %s\n", mixer_ctl->hdr.name); + goto out; + } + + reply = (struct ipc4_module_large_config_reply *)reply_data; + if (reply->primary.r.status != IPC4_SUCCESS) { + SNDERR("volume control %s set failed with status %d\n", + mixer_ctl->hdr.name, reply->primary.r.status); + err = -EINVAL; + goto out; + } + + /* check data sanity */ + num_data_items = reply->extension.r.data_off_size / sizeof(*volume); + if (num_data_items != mixer_ctl->num_channels) { + SNDERR("Channel count %d doesn't match the expected value %d\n", + num_data_items, mixer_ctl->num_channels); + err = -EINVAL; + goto out; + } + + /* set the mixer values based on the received data */ + volume = (struct ipc4_peak_volume_config *)(reply_data + sizeof(*reply)); + for (i = 0; i < mixer_ctl->num_channels; i++) + value[i] = ipc_to_mixer(volume[i].target_volume, ctl->glb->ctl[key].volume_table, + mixer_ctl->max + 1); +out: + free(reply_data); + return err; +} + +static int plug_ctl_write_integer(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, long *value) +{ + snd_sof_ctl_t *ctl = ext->private_data; + struct snd_soc_tplg_mixer_control *mixer_ctl = CTL_GET_TPLG_MIXER(ctl, key); + struct ipc4_module_large_config config = {{ 0 }}; + struct ipc4_peak_volume_config volume; + struct ipc4_message_reply reply; + bool all_channels_equal = true; + uint32_t val; + void *msg; + int size, err; + int i; + + /* set data for IPC */ + val = value[0]; + for (i = 1; i < mixer_ctl->num_channels; i++) { + if (value[i] != val) { + all_channels_equal = false; + break; + } + } + + /* + * if all channels have the same volume, send a single IPC, else, send individual IPCs + * for each channel + */ + for (i = 0; i < mixer_ctl->num_channels; i++) { + if (all_channels_equal) { + volume.channel_id = IPC4_ALL_CHANNELS_MASK; + volume.target_volume = mixer_to_ipc(val, ctl->glb->ctl[key].volume_table, + mixer_ctl->max + 1); + } else { + volume.channel_id = i; + volume.target_volume = mixer_to_ipc(value[i], + ctl->glb->ctl[key].volume_table, + mixer_ctl->max + 1); + } + + /* TODO: get curve duration and type from topology */ + volume.curve_type = 1; + volume.curve_duration = 200000; + + /* configure the IPC message */ + plug_ctl_ipc_message(&config, IPC4_VOLUME, sizeof(volume), + ctl->glb->ctl[key].module_id, ctl->glb->ctl[key].instance_id, + SOF_IPC4_MOD_LARGE_CONFIG_SET); + config.extension.r.final_block = 1; + config.extension.r.init_block = 1; + + size = sizeof(config) + sizeof(volume); + msg = calloc(size, 1); + if (!msg) + return -ENOMEM; + + memcpy(msg, &config, sizeof(config)); + memcpy(msg + sizeof(config), &volume, sizeof(volume)); + + /* send the message and check status */ + err = plug_ipc_cmd_tx_rx(&ctl->ipc, msg, size, &reply, sizeof(reply)); + free(msg); + if (err < 0) { + SNDERR("failed to set volume control %s\n", mixer_ctl->hdr.name); + return err; + } + + if (reply.primary.r.status != IPC4_SUCCESS) { + SNDERR("volume control %s set failed with status %d\n", + mixer_ctl->hdr.name, reply.primary.r.status); + return -EINVAL; + } + + if (all_channels_equal) + break; + } + + return 0; +} + +/* + * Enum ops + */ +static int plug_ctl_get_enumerated_info(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, + unsigned int *items) +{ + snd_sof_ctl_t *ctl = ext->private_data; + struct snd_soc_tplg_ctl_hdr *hdr = CTL_GET_TPLG_HDR(ctl, key); + struct snd_soc_tplg_enum_control *enum_ctl = + (struct snd_soc_tplg_enum_control *)hdr; + int err = 0; + + switch (hdr->ops.info) { + case SND_SOC_TPLG_CTL_ENUM: + case SND_SOC_TPLG_CTL_ENUM_VALUE: + *items = enum_ctl->items; + break; + default: + SNDERR("invalid ctl type for enum using key %d", key); + err = -EINVAL; + break; + } + + return err; +} + +static int plug_ctl_get_enumerated_name(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, + unsigned int item, char *name, size_t name_max_len) +{ + snd_sof_ctl_t *ctl = ext->private_data; + struct snd_soc_tplg_ctl_hdr *hdr = CTL_GET_TPLG_HDR(ctl, key); + struct snd_soc_tplg_enum_control *enum_ctl = + (struct snd_soc_tplg_enum_control *)hdr; + + if (item >= enum_ctl->items) { + SNDERR("invalid item %d for enum using key %d\n", item, key); + return -EINVAL; + } + + strncpy(name, enum_ctl->texts[item], name_max_len); + return 0; +} + +static int plug_ctl_read_enumerated(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, + unsigned int *items) +{ + snd_sof_ctl_t *ctl = ext->private_data; + struct snd_soc_tplg_enum_control *enum_ctl = CTL_GET_TPLG_ENUM(ctl, key); + struct ipc4_module_large_config config = {{ 0 }}; + struct ipc4_module_large_config_reply *reply; + struct sof_ipc4_control_msg_payload *data; + char *reply_data; + void *msg; + int size, reply_data_size; + int i, err; + + /* configure the IPC message */ + plug_ctl_ipc_message(&config, SOF_IPC4_ENUM_CONTROL_PARAM_ID, 0, + ctl->glb->ctl[key].module_id, ctl->glb->ctl[key].instance_id, + SOF_IPC4_MOD_LARGE_CONFIG_GET); + + config.extension.r.final_block = 1; + config.extension.r.init_block = 1; + + size = sizeof(config); + msg = calloc(size, 1); + if (!msg) + return -ENOMEM; + + /* reply contains both the requested data and the reply status */ + reply_data_size = sizeof(*reply) + sizeof(*data) + + enum_ctl->num_channels * sizeof(data->chanv[0]); + reply_data = calloc(reply_data_size, 1); + if (!reply_data_size) { + free(msg); + return -ENOMEM; + } + + /* send the IPC message */ + memcpy(msg, &config, sizeof(config)); + err = plug_ipc_cmd_tx_rx(&ctl->ipc, msg, size, reply_data, reply_data_size); + free(msg); + if (err < 0) { + SNDERR("failed to get enum items for control %s\n", enum_ctl->hdr.name); + goto out; + } + + reply = (struct ipc4_module_large_config_reply *)reply_data; + if (reply->primary.r.status != IPC4_SUCCESS) { + SNDERR("enum control %s get failed with status %d\n", + enum_ctl->hdr.name, reply->primary.r.status); + err = -EINVAL; + goto out; + } + + /* check data sanity */ + data = (struct sof_ipc4_control_msg_payload *)(reply_data + sizeof(*reply)); + if (data->num_elems != enum_ctl->num_channels) { + SNDERR("Channel count %d doesn't match the expected value %d for enum ctl %s\n", + data->num_elems, enum_ctl->num_channels, enum_ctl->hdr.name); + err = -EINVAL; + goto out; + } + + /* set the enum items based on the received data */ + for (i = 0; i < enum_ctl->num_channels; i++) + items[i] = data->chanv[i].value; +out: + free(reply_data); + return 0; +} + +static int plug_ctl_write_enumerated(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, + unsigned int *items) +{ + snd_sof_ctl_t *ctl = ext->private_data; + struct snd_soc_tplg_enum_control *enum_ctl = CTL_GET_TPLG_ENUM(ctl, key); + struct ipc4_module_large_config config = {{ 0 }}; + struct sof_ipc4_control_msg_payload *data; + struct ipc4_message_reply reply; + void *msg; + int data_size, msg_size; + int err, i; + + /* size of control data */ + data_size = enum_ctl->num_channels * sizeof(struct sof_ipc4_ctrl_value_chan) + + sizeof(*data); + + /* allocate memory for control data */ + data = calloc(data_size, 1); + if (!data) + return -ENOMEM; + + /* set param ID and number of channels */ + data->id = ctl->glb->ctl[key].index; + data->num_elems = enum_ctl->num_channels; + + /* set the enum values */ + for (i = 0; i < data->num_elems; i++) { + data->chanv[i].channel = i; + data->chanv[i].value = items[i]; + } + + /* configure the IPC message */ + plug_ctl_ipc_message(&config, SOF_IPC4_ENUM_CONTROL_PARAM_ID, data_size, + ctl->glb->ctl[key].module_id, ctl->glb->ctl[key].instance_id, + SOF_IPC4_MOD_LARGE_CONFIG_SET); + + /* + * enum controls can have a maximum of 16 texts/values. So the entire data can be sent + * in a single IPC message + */ + config.extension.r.final_block = 1; + config.extension.r.init_block = 1; + + /* allocate memory for IPC message */ + msg_size = sizeof(config) + data_size; + msg = calloc(msg_size, 1); + if (!msg) { + free(data); + return -ENOMEM; + } + + /* set the IPC message data */ + memcpy(msg, &config, sizeof(config)); + memcpy(msg + sizeof(config), data, data_size); + free(data); + + /* send the message and check status */ + err = plug_ipc_cmd_tx_rx(&ctl->ipc, msg, msg_size, &reply, sizeof(reply)); + free(msg); + if (err < 0) { + SNDERR("failed to set enum control %s\n", enum_ctl->hdr.name); + return err; + } + + if (reply.primary.r.status != IPC4_SUCCESS) { + SNDERR("enum control %s set failed with status %d\n", + enum_ctl->hdr.name, reply.primary.r.status); + return -EINVAL; + } + + return 0; +} + +/* + * Bytes ops + */ +static int plug_ctl_get_bytes_data(snd_sof_ctl_t *ctl, snd_ctl_ext_key_t key, + struct sof_abi_hdr *abi, unsigned int max_bytes) +{ + struct snd_soc_tplg_bytes_control *bytes_ctl = CTL_GET_TPLG_BYTES(ctl, key); + struct ipc4_module_large_config config = {{ 0 }}; + struct ipc4_module_large_config_reply *reply; + char *reply_data, *data; + void *msg; + uint32_t data_size; + int size, reply_data_size; + int err; + + /* configure the IPC message */ + plug_ctl_ipc_message(&config, abi->type, 0, + ctl->glb->ctl[key].module_id, ctl->glb->ctl[key].instance_id, + SOF_IPC4_MOD_LARGE_CONFIG_GET); + + config.extension.r.final_block = 1; + config.extension.r.init_block = 1; + + size = sizeof(config); + msg = calloc(size, 1); + if (!msg) + return -ENOMEM; + + /* + * reply contains both the requested data and the reply status. Allocate enough memory + * for max data + */ + reply_data_size = sizeof(*reply) + sizeof(*data) + bytes_ctl->max; + reply_data = calloc(reply_data_size, 1); + if (!reply_data_size) { + free(msg); + return -ENOMEM; + } + + /* send the IPC message */ + memcpy(msg, &config, sizeof(config)); + err = plug_ipc_cmd_tx_rx(&ctl->ipc, msg, size, reply_data, reply_data_size); + free(msg); + if (err < 0) { + SNDERR("failed to get bytes data for control %s\n", bytes_ctl->hdr.name); + goto out; + } + + reply = (struct ipc4_module_large_config_reply *)reply_data; + if (reply->primary.r.status != IPC4_SUCCESS) { + SNDERR("bytes control %s get failed with status %d\n", + bytes_ctl->hdr.name, reply->primary.r.status); + err = -EINVAL; + goto out; + } + + /* check data sanity */ + data = (char *)(reply_data + sizeof(*reply)); + data_size = reply->extension.r.data_off_size; + if (data_size > bytes_ctl->max) { + SNDERR("received data size %d is larger than max %d for bytes control %s\n", + data_size, bytes_ctl->max, bytes_ctl->hdr.name); + err = -EINVAL; + goto out; + } + + abi->size = data_size; + + if (data_size) + memcpy(abi->data, data, MIN(data_size, max_bytes)); + + err = data_size; +out: + free(reply_data); + return err; +} + +static int plug_ctl_read_bytes(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, + unsigned char *data, size_t max_bytes) +{ + snd_sof_ctl_t *ctl = ext->private_data; + struct sof_abi_hdr *abi = (struct sof_abi_hdr *)data; + int data_size; + + data_size = plug_ctl_get_bytes_data(ctl, key, abi, max_bytes); + if (data_size < 0) + return data_size; + + return 0; +} + +static int plug_ctl_write_bytes(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, + unsigned char *data, size_t max_bytes) +{ + snd_sof_ctl_t *ctl = ext->private_data; + struct snd_soc_tplg_bytes_control *bytes_ctl = CTL_GET_TPLG_BYTES(ctl, key); + struct sof_abi_hdr *abi = (struct sof_abi_hdr *)data; + int err; + + /* send IPC with kcontrol data */ + err = plug_send_bytes_data(&ctl->ipc, ctl->glb->ctl[key].module_id, + ctl->glb->ctl[key].instance_id, abi); + if (err < 0) { + SNDERR("failed to set bytes data for control %s\n", bytes_ctl->hdr.name); + return err; + } + + return 0; +} + +/* TLV ops used for TLV bytes control callback */ +/* TLV ops used for TLV bytes control callback */ +static int plug_tlv_rw(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, int op_flag, + unsigned int numid, unsigned int *tlv, unsigned int tlv_size) +{ + snd_sof_ctl_t *ctl = ext->private_data; + struct snd_soc_tplg_bytes_control *bytes_ctl = CTL_GET_TPLG_BYTES(ctl, key); + struct sof_abi_hdr *abi = (struct sof_abi_hdr *)(tlv + 2); /* skip TLV header */ + int data_size; + + /* only bytes and volume controls have tlv callback set */ + if (bytes_ctl->hdr.ops.info != SND_SOC_TPLG_CTL_BYTES) { + struct snd_soc_tplg_mixer_control *mixer_ctl = CTL_GET_TPLG_MIXER(ctl, key); + struct snd_soc_tplg_ctl_hdr *hdr = &mixer_ctl->hdr; + struct snd_soc_tplg_ctl_tlv *mixer_tlv = &hdr->tlv; + + /* set the dbscale values */ + tlv[0] = SND_CTL_TLVT_DB_SCALE; + tlv[1] = sizeof(int) * 2; + tlv[2] = mixer_tlv->scale.min; + tlv[3] = mixer_tlv->scale.mute << 16 | mixer_tlv->scale.step; + + return 0; + } + + /* send IPC with kcontrol data if op_flag is > 0 else send IPC to get kcontrol data */ + if (op_flag) { + int err; + + err = plug_send_bytes_data(&ctl->ipc, ctl->glb->ctl[key].module_id, + ctl->glb->ctl[key].instance_id, abi); + if (err < 0) { + SNDERR("failed to set bytes data for control %s\n", bytes_ctl->hdr.name); + return err; + } + + return 0; + } + + /* read kcontrol data */ + data_size = plug_ctl_get_bytes_data(ctl, key, abi, tlv_size); + if (data_size < 0) + return data_size; + + /* set data size and numid */ + tlv[0] = numid; + tlv[1] = data_size + sizeof(*abi); + + return 0; +} + +static void plug_ctl_subscribe_events(snd_ctl_ext_t *ext, int subscribe) +{ + snd_sof_ctl_t *ctl = ext->private_data; + + ctl->subscribed = !!(subscribe & SND_CTL_EVENT_MASK_VALUE); +} + +static int plug_ctl_read_event(snd_ctl_ext_t *ext, snd_ctl_elem_id_t *id, + unsigned int *event_mask) +{ + snd_sof_ctl_t *ctl = ext->private_data; + int numid; + int err = 0; + + numid = snd_ctl_elem_id_get_numid(id); + + // TODO: we need a notify() or listening thread to take async/volatile ctl + // notifications from sof-pipe and notify userspace via events of the ctl change. + if (!ctl->updated[numid - 1] || !ctl->subscribed) { + err = -EAGAIN; + goto out; + } + + *event_mask = SND_CTL_EVENT_MASK_VALUE; +out: + return err; +} + +static int plug_ctl_poll_revents(snd_ctl_ext_t *ext, struct pollfd *pfd, + unsigned int nfds, unsigned short *revents) +{ + snd_sof_ctl_t *ctl = ext->private_data; + int i; + + *revents = 0; + + for (i = 0; i < ctl->glb->num_ctls; i++) { + if (ctl->updated[i]) { + *revents = POLLIN; + break; + } + } + + return 0; +} + +static void plug_ctl_close(snd_ctl_ext_t *ext) +{ + snd_sof_ctl_t *ctl = ext->private_data; + + /* TODO: munmap */ + close(ctl->ipc.socket_fd); + free(ctl); +} + +static const snd_ctl_ext_callback_t sof_ext_callback = { + .elem_count = plug_ctl_elem_count, + .elem_list = plug_ctl_elem_list, + .find_elem = plug_ctl_find_elem, + .get_attribute = plug_ctl_get_attribute, + .get_integer_info = plug_ctl_get_integer_info, + .read_integer = plug_ctl_read_integer, + .write_integer = plug_ctl_write_integer, + .get_enumerated_info = plug_ctl_get_enumerated_info, + .get_enumerated_name = plug_ctl_get_enumerated_name, + .read_enumerated = plug_ctl_read_enumerated, + .write_enumerated = plug_ctl_write_enumerated, + .read_bytes = plug_ctl_read_bytes, + .write_bytes = plug_ctl_write_bytes, + .subscribe_events = plug_ctl_subscribe_events, + .read_event = plug_ctl_read_event, + .poll_revents = plug_ctl_poll_revents, + .close = plug_ctl_close, +}; + +SND_CTL_PLUGIN_DEFINE_FUNC(sof) +{ + snd_sof_plug_t *plug; + int err; + snd_sof_ctl_t *ctl; + + /* create context */ + plug = calloc(1, sizeof(*plug)); + if (!plug) + return -ENOMEM; + + ctl = calloc(1, sizeof(*ctl)); + if (!ctl) + return -ENOMEM; + plug->module_prv = ctl; + + /* parse the ALSA configuration file for sof plugin */ + err = plug_parse_conf(plug, name, root, conf, true); + if (err < 0) { + SNDERR("failed to parse config: %s", strerror(err)); + goto error; + } + + /* init IPC socket name */ + err = plug_socket_path_init(&ctl->ipc, "sof", "ipc", 0); + if (err < 0) { + SNDERR("error: invalid name for IPC tx mq %s\n", plug->tplg_file); + goto error; + } + + err = plug_create_client_socket(&ctl->ipc); + if (err < 0) { + SNDERR("failed to connect to SOF pipe IPC socket : %s", strerror(err)); + return -errno; + } + + /* create a SHM mapping for low latency stream position */ + err = plug_shm_init(&ctl->shm_ctx, plug->tplg_file, "ctx", 0); + if (err < 0) + goto error; + + // TODO: make this open/close per operation for shared access + /* create a SHM mapping for low latency stream position */ + err = plug_shm_open(&ctl->shm_ctx); + if (err < 0) + goto error; + + /* get global context for kcontrol lookup */ + ctl->glb = ctl->shm_ctx.addr; + + /* TODO: add some flavour to the names based on the topology */ + ctl->ext.version = SND_CTL_EXT_VERSION; + ctl->ext.card_idx = 0; + strncpy(ctl->ext.id, "sof", sizeof(ctl->ext.id) - 1); + strncpy(ctl->ext.driver, "SOF plugin", + sizeof(ctl->ext.driver) - 1); + strncpy(ctl->ext.name, "SOF", sizeof(ctl->ext.name) - 1); + strncpy(ctl->ext.mixername, "SOF", + sizeof(ctl->ext.mixername) - 1); + + ctl->ext.callback = &sof_ext_callback; + ctl->ext.private_data = ctl; + ctl->ext.tlv.c = plug_tlv_rw; + + err = snd_ctl_ext_create(&ctl->ext, name, mode); + if (err < 0) + goto error; + + *handlep = ctl->ext.handle; + + return 0; + +error: + free(ctl); + + return err; +} + +SND_CTL_PLUGIN_SYMBOL(sof); diff --git a/tools/plugin/alsaplug/pcm.c b/tools/plugin/alsaplug/pcm.c new file mode 100644 index 000000000000..ed5224c829b5 --- /dev/null +++ b/tools/plugin/alsaplug/pcm.c @@ -0,0 +1,1000 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2022 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include "plugin.h" +#include "common.h" + +typedef struct snd_sof_pcm { + snd_pcm_ioplug_t io; + size_t frame_size; + struct timespec wait_timeout; + int capture; + int events; + + /* PCM flow control */ + struct plug_sem_desc ready[TPLG_MAX_PCM_PIPELINES]; + struct plug_sem_desc done[TPLG_MAX_PCM_PIPELINES]; + + struct plug_shm_desc shm_pcm; + + int frame_us; +} snd_sof_pcm_t; + +static int plug_pipeline_set_state(snd_sof_plug_t *plug, int state, + struct ipc4_pipeline_set_state *pipe_state, + struct tplg_pipeline_info *pipe_info, + struct plug_socket_desc *ipc) +{ + struct ipc4_message_reply reply = {{ 0 }}; + int ret; + + pipe_state->primary.r.ppl_id = pipe_info->instance_id; + + ret = plug_ipc_cmd_tx_rx(ipc, pipe_state, sizeof(*pipe_state), + &reply, sizeof(reply)); + if (ret < 0) + SNDERR("failed pipeline %d set state %d\n", pipe_info->instance_id, state); + + return ret; +} + +static int plug_pipelines_set_state(snd_sof_plug_t *plug, int state) +{ + snd_sof_pcm_t *pcm = plug->module_prv; + struct ipc4_pipeline_set_state pipe_state = {{ 0 }}; + struct tplg_pipeline_list *pipeline_list; + int i; + + if (pcm->capture) + pipeline_list = &plug->pcm_info->capture_pipeline_list; + else + pipeline_list = &plug->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 (pcm->capture) { + for (i = pipeline_list->count - 1; i >= 0; i--) { + struct tplg_pipeline_info *pipe_info = pipeline_list->pipelines[i]; + int ret; + + ret = plug_pipeline_set_state(plug, state, &pipe_state, pipe_info, + &plug->ipc); + if (ret < 0) + return ret; + } + + return 0; + } + + for (i = 0; i < pipeline_list->count; i++) { + struct tplg_pipeline_info *pipe_info = pipeline_list->pipelines[i]; + int ret; + + ret = plug_pipeline_set_state(plug, state, &pipe_state, pipe_info, + &plug->ipc); + if (ret < 0) + return ret; + } + + return 0; +} + +static int plug_pcm_start(snd_pcm_ioplug_t *io) +{ + snd_sof_plug_t *plug = io->private_data; + snd_sof_pcm_t *pcm = plug->module_prv; + struct plug_shm_endpoint *ctx = pcm->shm_pcm.addr; + int err; + + switch (ctx->state) { + case SOF_PLUGIN_STATE_READY: + err = plug_pipelines_set_state(plug, SOF_IPC4_PIPELINE_STATE_RUNNING); + if (err < 0) + return err; + break; + case SOF_PLUGIN_STATE_STREAM_RUNNING: + { + struct tplg_pipeline_list *pipeline_list; + int i, delay; + + if (!pcm->capture) + break; + + pipeline_list = &plug->pcm_info->capture_pipeline_list; + + /* start the first period copy for capture */ + for (i = pipeline_list->count - 1; i >= 0; i--) { + sem_post(pcm->ready[i].sem); + + /* wait for sof-pipe reader to consume data or timeout */ + err = clock_gettime(CLOCK_REALTIME, &pcm->wait_timeout); + if (err == -1) { + SNDERR("write: cant get time: %s", strerror(errno)); + return -EPIPE; + } + + /* work out delay TODO: fix ALSA reader */ + delay = pcm->frame_us * io->period_size / 500; + plug_timespec_add_ms(&pcm->wait_timeout, delay); + + /* wait for sof-pipe writer to produce data or timeout */ + err = sem_timedwait(pcm->done[i].sem, &pcm->wait_timeout); + if (err == -1) { + SNDERR("read: waited %d ms for %ld frames fatal timeout: %s", + delay, io->period_size, strerror(errno)); + return -errno; + } + } + } + break; + case SOF_PLUGIN_STATE_INIT: + case SOF_PLUGIN_STATE_STREAM_ERROR: + case SOF_PLUGIN_STATE_DEAD: + default: + /* some error */ + SNDERR("pcm start: invalid pipe state: %d", ctx->state); + return -EINVAL; + } + + return 0; +} + +static int plug_pcm_stop(snd_pcm_ioplug_t *io) +{ + snd_sof_plug_t *plug = io->private_data; + snd_sof_pcm_t *pcm = plug->module_prv; + struct plug_shm_endpoint *ctx = pcm->shm_pcm.addr; + int err; + + printf("%s %d state %ld\n", __func__, __LINE__, ctx->state); + switch (ctx->state) { + case SOF_PLUGIN_STATE_STREAM_ERROR: + case SOF_PLUGIN_STATE_STREAM_RUNNING: + { + err = plug_pipelines_set_state(plug, SOF_IPC4_PIPELINE_STATE_PAUSED); + if (err < 0) + return err; + break; + } + case SOF_PLUGIN_STATE_READY: + /* already stopped */ + break; + case SOF_PLUGIN_STATE_INIT: + case SOF_PLUGIN_STATE_DEAD: + default: + /* some error */ + SNDERR("pcm stop: invalid pipe state: %d", ctx->state); + return -EINVAL; + } + + return 0; +} + +/* buffer position up to buffer_size */ +static snd_pcm_sframes_t plug_pcm_pointer(snd_pcm_ioplug_t *io) +{ + snd_sof_plug_t *plug = io->private_data; + snd_sof_pcm_t *pcm = plug->module_prv; + struct plug_shm_endpoint *ctx = pcm->shm_pcm.addr; + snd_pcm_sframes_t ptr = 0; + int err; + + if (io->state == SND_PCM_STATE_XRUN) + return -EPIPE; + + if (io->state != SND_PCM_STATE_RUNNING) + return 0; + + switch (ctx->state) { + case SOF_PLUGIN_STATE_STREAM_RUNNING: + case SOF_PLUGIN_STATE_STREAM_ERROR: + if (pcm->capture) + ptr = ctx->wtotal / pcm->frame_size; + else + ptr = ctx->rtotal / pcm->frame_size; + break; + case SOF_PLUGIN_STATE_READY: + /* not running */ + return 0; + case SOF_PLUGIN_STATE_INIT: + case SOF_PLUGIN_STATE_DEAD: + default: + /* some error */ + SNDERR("pointer: invalid pipe state: %d", ctx->state); + ptr = -EPIPE; + } + + return ptr; +} + +/* get the delay for the running PCM; optional; since v1.0.1 */ +static int plug_pcm_delay(snd_pcm_ioplug_t *io, snd_pcm_sframes_t *delayp) +{ + snd_sof_plug_t *plug = io->private_data; + snd_sof_pcm_t *pcm = plug->module_prv; + struct plug_shm_endpoint *ctx = pcm->shm_pcm.addr; + int err = 0; + + switch (ctx->state) { + case SOF_PLUGIN_STATE_STREAM_RUNNING: + case SOF_PLUGIN_STATE_READY: + // TODO: is capture delay correct here ??? + if (pcm->capture) + *delayp = (ctx->wtotal - ctx->rtotal) / pcm->frame_size; + else + *delayp = (ctx->rtotal - ctx->wtotal) / pcm->frame_size; + /* sanitize delay */ + if (*delayp < pcm->io.period_size || *delayp > io->buffer_size) + *delayp = pcm->io.period_size; + return 0; + case SOF_PLUGIN_STATE_STREAM_ERROR: + snd_pcm_ioplug_set_state(io, SND_PCM_STATE_XRUN); + return 0; + case SOF_PLUGIN_STATE_INIT: + case SOF_PLUGIN_STATE_DEAD: + default: + /* some error */ + SNDERR("delay: invalid pipe state: %d", ctx->state); + return -EPIPE; + } +} + +/* return frames written */ +static snd_pcm_sframes_t plug_pcm_write(snd_pcm_ioplug_t *io, const snd_pcm_channel_area_t *areas, + snd_pcm_uframes_t offset, snd_pcm_uframes_t size) +{ + snd_sof_plug_t *plug = io->private_data; + snd_sof_pcm_t *pcm = plug->module_prv; + struct plug_shm_endpoint *ctx = pcm->shm_pcm.addr; + struct tplg_pipeline_list *pipeline_list; + snd_pcm_sframes_t frames = 0; + int i; + ssize_t bytes; + const char *buf; + int err, delay; + + pipeline_list = &plug->pcm_info->playback_pipeline_list; + + /* calculate the buffer position and size from application */ + buf = (char *)areas->addr + (areas->first + areas->step * offset) / 8; + bytes = size * pcm->frame_size; + + /* now check what the pipe has free */ + bytes = MIN(plug_ep_get_free(ctx), bytes); + + frames = bytes / pcm->frame_size; + + if (frames == 0) + return frames; + + /* write audio data to the pipe */ + memcpy(plug_ep_wptr(ctx), buf, bytes); + + plug_ep_produce(ctx, bytes); + + /* tell the pipelines data is ready starting at the source pipeline */ + for (i = 0; i < pipeline_list->count; i++) { + struct tplg_pipeline_info *pipe_info = pipeline_list->pipelines[i]; + + sem_post(pcm->ready[i].sem); + + /* wait for sof-pipe reader to consume data or timeout */ + err = clock_gettime(CLOCK_REALTIME, &pcm->wait_timeout); + if (err == -1) { + SNDERR("write: cant get time: %s", strerror(errno)); + return -EPIPE; + } + + /* work out delay */ + delay = pcm->frame_us * frames / 500; + plug_timespec_add_ms(&pcm->wait_timeout, delay); + + /* now block caller on pipeline IO to PCM device */ + err = sem_timedwait(pcm->done[i].sem, &pcm->wait_timeout); + if (err == -1) { + SNDERR("write: waited %d ms for %ld frames, fatal timeout: %s", + delay, frames, strerror(errno)); + return -errno; + } + } + + return frames; +} + +/* return frames read */ +static snd_pcm_sframes_t plug_pcm_read(snd_pcm_ioplug_t *io, const snd_pcm_channel_area_t *areas, + snd_pcm_uframes_t offset, snd_pcm_uframes_t size) +{ + snd_sof_plug_t *plug = io->private_data; + snd_sof_pcm_t *pcm = plug->module_prv; + snd_pcm_sframes_t frames; + struct plug_shm_endpoint *ctx = pcm->shm_pcm.addr; + struct tplg_pipeline_list *pipeline_list; + ssize_t bytes; + char *buf; + int err, delay, i; + + pipeline_list = &plug->pcm_info->capture_pipeline_list; + + /* calculate the buffer position and size */ + buf = (char *)areas->addr + (areas->first + areas->step * offset) / 8; + bytes = size * pcm->frame_size; + + /* check what the pipe has avail */ + bytes = MIN(plug_ep_get_avail(ctx), bytes); + frames = bytes / pcm->frame_size; + + if (!frames) + return 0; + + /* tell the pipe ready we are ready for next period */ + for (i = pipeline_list->count - 1; i >= 0; i--) { + sem_post(pcm->ready[i].sem); + + /* wait for sof-pipe reader to consume data or timeout */ + err = clock_gettime(CLOCK_REALTIME, &pcm->wait_timeout); + if (err == -1) { + SNDERR("write: cant get time: %s", strerror(errno)); + return -EPIPE; + } + + /* work out delay TODO: fix ALSA reader */ + delay = pcm->frame_us * frames / 500; + plug_timespec_add_ms(&pcm->wait_timeout, delay); + + /* wait for sof-pipe writer to produce data or timeout */ + err = sem_timedwait(pcm->done[i].sem, &pcm->wait_timeout); + if (err == -1) { + SNDERR("read: waited %d ms for %ld frames fatal timeout: %s", + delay, frames, strerror(errno)); + return -errno; + } + } + + /* copy audio data from pipe */ + memcpy(buf, plug_ep_rptr(ctx), bytes); + plug_ep_consume(ctx, bytes); + + return frames; +} + +static int plug_pcm_prepare(snd_pcm_ioplug_t *io) +{ + snd_sof_plug_t *plug = io->private_data; + snd_sof_pcm_t *pcm = plug->module_prv; + struct plug_shm_endpoint *ctx = pcm->shm_pcm.addr; + int err = 0; + + ctx->wtotal = 0; + ctx->rtotal = 0; + ctx->rpos = 0; + ctx->rwrap = 0; + ctx->wpos = 0; + ctx->wwrap = 0; + + /* start the pipeline threads + * + * We do this during prepare so that pipelines can consume/produce + * any start threshold worth of data with the pipeline in a running + * state + */ + switch (ctx->state) { + case SOF_PLUGIN_STATE_INIT: + /* set pipelines to PAUSED state to prepare them */ + err = plug_pipelines_set_state(plug, SOF_IPC4_PIPELINE_STATE_PAUSED); + if (err < 0) + return err; + + fprintf(stdout, "pipelines complete now\n"); + + /* set pipelines to RUNNING state */ + err = plug_pipelines_set_state(plug, SOF_IPC4_PIPELINE_STATE_RUNNING); + if (err < 0) + return err; + break; + case SOF_PLUGIN_STATE_STREAM_ERROR: + case SOF_PLUGIN_STATE_DEAD: + /* some error */ + SNDERR("write: invalid pipe state: %d", ctx->state); + return -EINVAL; + case SOF_PLUGIN_STATE_READY: + case SOF_PLUGIN_STATE_STREAM_RUNNING: + default: + /* do nothing */ + break; + } + + fprintf(stdout, "PCM prepare done\n"); + + return err; +} + +static int plug_init_shm_ctx(snd_sof_plug_t *plug, snd_pcm_hw_params_t *params); + +static int plug_pcm_hw_params(snd_pcm_ioplug_t *io, snd_pcm_hw_params_t *params) +{ + snd_sof_plug_t *plug = io->private_data; + snd_sof_pcm_t *pcm = plug->module_prv; + struct plug_shm_endpoint *ctx; + struct tplg_pipeline_list *pipeline_list; + int i, err; + + pcm->frame_size = (snd_pcm_format_physical_width(io->format) * io->channels) / 8; + + plug->period_size = io->period_size; + + /* used for wait timeouts */ + pcm->frame_us = ceil(1000000.0 / io->rate); + + /* now send IPCs to set up widgets */ + err = plug_set_up_pipelines(plug, pcm->capture, params); + if (err < 0) { + fprintf(stderr, "error setting up pipelines\n"); + return err; + } + + if (pcm->capture) + pipeline_list = &plug->pcm_info->capture_pipeline_list; + else + pipeline_list = &plug->pcm_info->playback_pipeline_list; + + /* init and open for PCM ready lock for all pipelines */ + for (i = 0; i < pipeline_list->count; i++) { + struct tplg_pipeline_info *pipe_info = pipeline_list->pipelines[i]; + + /* init name for pipeline ready lock */ + err = plug_lock_init(&pcm->ready[i], plug->tplg_file, "ready", + pipe_info->instance_id); + if (err < 0) { + SNDERR("error: invalid name for PCM ready lock %s\n", + pipe_info->instance_id); + return err; + } + + /* init name for pipeline done lock */ + err = plug_lock_init(&pcm->done[i], plug->tplg_file, "done", + pipe_info->instance_id); + if (err < 0) { + SNDERR("error: invalid name for PCM done lock %s\n", + pipe_info->instance_id); + return err; + } + + /* open lock "ready" for pipeline audio data */ + err = plug_lock_open(&pcm->ready[i]); + if (err < 0) { + SNDERR("error: failed to open sof-pipe ready lock %s: %s", + pcm->ready[i].name, strerror(err)); + return -errno; + } + + /* open lock "done" for pipeline audio data */ + err = plug_lock_open(&pcm->done[i]); + if (err < 0) { + SNDERR("error: failed to open sof-pipe done lock %s: %s", + pcm->done[i].name, strerror(err)); + return -errno; + } + } + + /* init PCM shm name */ + err = plug_shm_init(&pcm->shm_pcm, plug->tplg_file, "pcm", plug->pcm_id); + if (err < 0) { + SNDERR("error: invalid name for PCM SHM %s\n", plug->tplg_file); + return err; + } + + /* open audio PCM SHM data endpoint */ + err = plug_shm_open(&pcm->shm_pcm); + if (err < 0) { + SNDERR("error: failed to open sof-pipe PCM SHM %s: %s", + pcm->shm_pcm.name, strerror(err)); + return -errno; + } + + /* set up the endpoint configs */ + err = plug_init_shm_ctx(plug, params); + if (err < 0) { + SNDERR("error: failed to init sof-pipe ep context: %s:%s", + pcm->shm_pcm.name, strerror(err)); + return -err; + } + + ctx = pcm->shm_pcm.addr; + ctx->frame_size = pcm->frame_size; + + /* needs to be set here and NOT in SW params as SW params not + * called in capture flow ? + */ + ctx->buffer_size = io->buffer_size * ctx->frame_size; + + if (!ctx->buffer_size) { + SNDERR("Invalid buffer_size io buffer_size %d ctx->frame_size %d\n", + io->buffer_size, ctx->frame_size); + return -EINVAL; + } + + fprintf(stdout, "PCM hw_params done\n"); + + return 0; +} + +// TODO: why not called for arecord +static int plug_pcm_sw_params(snd_pcm_ioplug_t *io, snd_pcm_sw_params_t *params) +{ + snd_sof_plug_t *plug = io->private_data; + snd_sof_pcm_t *pcm = plug->module_prv; + snd_pcm_uframes_t start_threshold; + struct plug_shm_endpoint *ctx = pcm->shm_pcm.addr; + int err; + + /* get the stream start threshold */ + err = snd_pcm_sw_params_get_start_threshold(params, &start_threshold); + if (err < 0) { + SNDERR("sw params: failed to get start threshold: %s", strerror(err)); + return err; + } + + /* TODO: this seems to be ignored or overridden by application params ??? */ + if (start_threshold < io->period_size) { + start_threshold = io->period_size; + err = snd_pcm_sw_params_set_start_threshold(pcm->io.pcm, + params, start_threshold); + if (err < 0) { + SNDERR("sw params: failed to set start threshold %d: %s", + start_threshold, strerror(err)); + return err; + } + } + + /* keep running as long as we can */ + err = snd_pcm_sw_params_set_avail_min(pcm->io.pcm, params, 1); + if (err < 0) { + SNDERR("sw params: failed to set avail min %d: %s", 1, strerror(err)); + return err; + } + + fprintf(stdout, "sw_params done\n"); + + return 0; +} + +static int plug_pcm_close(snd_pcm_ioplug_t *io) +{ + snd_sof_plug_t *plug = io->private_data; + snd_sof_pcm_t *pcm = plug->module_prv; + struct plug_shm_glb_state *ctx = plug->glb_ctx.addr; + int err = 0; + + printf("%s %d\n", __func__, __LINE__); + + ctx->state = SOF_PLUGIN_STATE_INIT; + + plug_free_topology(plug); + + free(plug->tplg_file); + free(plug->module_prv); + free(plug); + + return err; +} + +static int plug_pcm_hw_free(snd_pcm_ioplug_t *io) +{ + struct tplg_pipeline_list *pipeline_list; + snd_sof_plug_t *plug = io->private_data; + snd_sof_pcm_t *pcm = plug->module_prv; + int ret, i; + + /* reset all pipelines */ + ret = plug_pipelines_set_state(plug, SOF_IPC4_PIPELINE_STATE_RESET); + if (ret < 0) { + fprintf(stderr, "failed to reset pipelines\n"); + return ret; + } + + if (pcm->capture) + pipeline_list = &plug->pcm_info->capture_pipeline_list; + else + pipeline_list = &plug->pcm_info->playback_pipeline_list; + + ret = plug_free_pipelines(plug, pipeline_list, pcm->capture); + + close(pcm->shm_pcm.fd); + close(plug->glb_ctx.fd); + + for (i = 0; i < pipeline_list->count; i++) { + struct tplg_pipeline_info *pipe_info = pipeline_list->pipelines[i]; + + sem_close(pcm->ready[pipe_info->instance_id].sem); + sem_close(pcm->done[pipe_info->instance_id].sem); + } + close(plug->ipc.socket_fd); + + return 0; +} + +static const snd_pcm_ioplug_callback_t sof_playback_callback = { + .start = plug_pcm_start, + .stop = plug_pcm_stop, + .pointer = plug_pcm_pointer, + .transfer = plug_pcm_write, + .delay = plug_pcm_delay, + .prepare = plug_pcm_prepare, + .hw_params = plug_pcm_hw_params, + .hw_free = plug_pcm_hw_free, + .sw_params = plug_pcm_sw_params, + .close = plug_pcm_close, +}; + +static const snd_pcm_ioplug_callback_t sof_capture_callback = { + .start = plug_pcm_start, + .stop = plug_pcm_stop, + .pointer = plug_pcm_pointer, + .transfer = plug_pcm_read, + .delay = plug_pcm_delay, + .prepare = plug_pcm_prepare, + .hw_params = plug_pcm_hw_params, + .sw_params = plug_pcm_sw_params, + .hw_free = plug_pcm_hw_free, + .close = plug_pcm_close, +}; + +static const snd_pcm_access_t access_list[] = { + SND_PCM_ACCESS_RW_INTERLEAVED +}; + +static const unsigned int formats[] = { + SND_PCM_FORMAT_S16_LE, + SND_PCM_FORMAT_FLOAT_LE, + SND_PCM_FORMAT_S32_LE, + SND_PCM_FORMAT_S24_LE, +}; + +/* + * Set HW constraints for the SOF plugin. This needs to be quite unrestrictive atm + * as we really need to parse topology before the HW constraints can be narrowed + * to a range that will work with the specified pipeline. + * TODO: Align with topology. + */ +static int plug_hw_constraint(snd_sof_plug_t *plug) +{ + snd_sof_pcm_t *pcm = plug->module_prv; + snd_pcm_ioplug_t *io = &pcm->io; + int err; + + err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_ACCESS, + ARRAY_SIZE(access_list), + access_list); + if (err < 0) { + SNDERR("constraints: failed to set access: %s", strerror(err)); + return err; + } + + err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_FORMAT, + ARRAY_SIZE(formats), formats); + if (err < 0) { + SNDERR("constraints: failed to set format: %s", strerror(err)); + return err; + } + + err = + snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_CHANNELS, + 1, 8); + if (err < 0) { + SNDERR("constraints: failed to set channels: %s", strerror(err)); + return err; + } + + err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_RATE, + 1, 192000); + if (err < 0) { + SNDERR("constraints: failed to set rate: %s", strerror(err)); + return err; + } + + err = + snd_pcm_ioplug_set_param_minmax(io, + SND_PCM_IOPLUG_HW_BUFFER_BYTES, + 1, 4 * 1024 * 1024); + if (err < 0) { + SNDERR("constraints: failed to set buffer bytes: %s", strerror(err)); + return err; + } + + err = + snd_pcm_ioplug_set_param_minmax(io, + SND_PCM_IOPLUG_HW_PERIOD_BYTES, + 128, 2 * 1024 * 1024); + if (err < 0) { + SNDERR("constraints: failed to set period bytes: %s", strerror(err)); + return err; + } + + err = + snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_PERIODS, 1, 4); + if (err < 0) { + SNDERR("constraints: failed to set period count: %s", strerror(err)); + return err; + } + + return 0; +} + +/* + * Register the plugin with ALSA and make available for use. + * TODO: setup all audio params + * TODO: setup polling fd for RW or mmap IOs + */ +static int plug_create(snd_sof_plug_t *plug, snd_pcm_t **pcmp, const char *name, + snd_pcm_stream_t stream, int mode) +{ + snd_sof_pcm_t *pcm = plug->module_prv; + int err; + + pcm->io.version = SND_PCM_IOPLUG_VERSION; + pcm->io.name = "ALSA <-> SOF PCM I/O Plugin"; + pcm->io.poll_fd = pcm->shm_pcm.fd; + pcm->io.poll_events = POLLIN; + pcm->io.mmap_rw = 0; + + if (stream == SND_PCM_STREAM_PLAYBACK) + pcm->io.callback = &sof_playback_callback; + else + pcm->io.callback = &sof_capture_callback; + + pcm->io.private_data = plug; + + /* create the plugin */ + err = snd_pcm_ioplug_create(&pcm->io, name, stream, mode); + if (err < 0) { + SNDERR("failed to register plugin %s: %s\n", name, strerror(err)); + return err; + } + + /* set the HW constrainst */ + err = plug_hw_constraint(plug); + if (err < 0) { + snd_pcm_ioplug_delete(&pcm->io); + return err; + } + + *pcmp = pcm->io.pcm; + return 0; +} + +static int plug_init_shm_ctx(snd_sof_plug_t *plug, snd_pcm_hw_params_t *params) +{ + struct plug_shm_glb_state *glb = plug->glb_ctx.addr; + struct endpoint_hw_config *ep; + struct plug_cmdline_item *ci; + struct plug_config *pc; + int i, j; + + glb->num_ep_configs = 0; + + for (i = 0; i < plug->num_cmdline; i++) { + bool found = false; + + if (glb->num_ep_configs >= NUM_EP_CONFIGS - 1) { + SNDERR("error: too many endpoint configs\n"); + return -EINVAL; + } + + ci = &plug->cmdline[i]; + + for (j = 0; j < plug->num_configs; j++) { + pc = &plug->config[j]; + if (strcmp(pc->name, ci->config_name)) + continue; + + ep = &glb->ep_config[glb->num_ep_configs++]; + ep->buffer_frames = pc->buffer_frames; + ep->buffer_time = pc->buffer_time; + ep->channels = pc->channels; + ep->format = pc->format; + ep->period_frames = pc->period_frames; + ep->period_time = pc->period_time; + ep->rate = pc->rate; + strncpy(ep->config_name, ci->config_name, sizeof(ep->config_name)); + found = true; + break; + } + + /* use hw_params if no matching config found or config missing in command line */ + if (!found) { + unsigned int channels, rate, dir, buffer_time, period_time; + snd_pcm_uframes_t buffer_size, period_size; + snd_pcm_format_t format; + + snd_pcm_hw_params_get_channels(params, &channels); + snd_pcm_hw_params_get_rate(params, &rate, &dir); + snd_pcm_hw_params_get_buffer_size(params, &buffer_size); + snd_pcm_hw_params_get_period_size(params, &period_size, &dir); + snd_pcm_hw_params_get_buffer_time(params, &buffer_time, &dir); + snd_pcm_hw_params_get_period_time(params, &period_time, &dir); + snd_pcm_hw_params_get_format(params, &format); + + ep = &glb->ep_config[glb->num_ep_configs++]; + ep->buffer_frames = buffer_size; + ep->buffer_time = buffer_time; + ep->channels = channels; + ep->format = format; + ep->period_frames = period_size; + ep->period_time = period_time; + ep->rate = rate; + } + + ep->pipeline = ci->pcm; + strncpy(ep->card_name, ci->card_name, sizeof(ep->card_name)); + strncpy(ep->dev_name, ci->dev_name, sizeof(ep->dev_name)); + } + + return 0; +} + +/* + * Complete parent initialisation. + * 1. Check if pipe already ready by opening SHM context and IPC. + * 2. TODO: check context state and load topology is needed for core. + */ +static int plug_init_sof_pipe(snd_sof_plug_t *plug, snd_pcm_t **pcmp, + const char *name, snd_pcm_stream_t stream, int mode) +{ + snd_sof_pcm_t *pcm = plug->module_prv; + struct plug_shm_glb_state *ctx; + struct timespec delay; + int err, i; + + /* initialize widget, route and pcm lists */ + list_init(&plug->widget_list); + list_init(&plug->route_list); + list_init(&plug->pcm_list); + + plug->tplg.tplg_file = plug->tplg_file; + + /* plugin only works with IPC4 */ + plug->tplg.ipc_major = 4; + + /* init global status hsm name */ + err = plug_shm_init(&plug->glb_ctx, plug->tplg_file, "ctx", 0); + if (err < 0) { + SNDERR("error: invalid name for global SHM %s\n", plug->tplg_file); + return err; + } + + /* open the global context via SHM */ + plug->glb_ctx.size = 128 * 1024; + err = plug_shm_open(&plug->glb_ctx); + if (err < 0) { + SNDERR("error: failed to open plugin context: %s:%s", + plug->glb_ctx.name, strerror(err)); + return err; + } + + /* parse topology file */ + err = plug_parse_topology(plug); + if (err < 0) { + fprintf(stderr, "error parsing topology %s\n", plug->tplg.tplg_file); + return err; + } + + fprintf(stdout, "topology parsing complete\n"); + + /* init IPC message queue name */ + err = plug_socket_path_init(&plug->ipc, "sof", "ipc", 0); + if (err < 0) { + SNDERR("error: invalid name for IPC socket %s\n", plug->tplg_file); + return err; + } + + err = plug_create_client_socket(&plug->ipc); + if (err < 0) { + SNDERR("failed to connect to SOF pipe IPC socket : %s", strerror(err)); + return -errno; + } + + /* now register the plugin */ + err = plug_create(plug, pcmp, name, stream, mode); + if (err < 0) { + SNDERR("failed to create plugin: %s", strerror(err)); + return -errno; + } + + return 0; +} + +/* + * ALSA PCM plugin entry point. + */ +SND_PCM_PLUGIN_DEFINE_FUNC(sof) +{ + snd_sof_plug_t *plug; + snd_sof_pcm_t *pcm; + int err; + + fprintf(stdout, "This code is WIP. Cmd args & config will possible change over time\n"); + fprintf(stdout, "\nThe 50-sonf.conf file is parsed for PCM configurations which can\n"); + fprintf(stdout, "be mapped on the cmd line to pipeline endpoints.\n"); + fprintf(stdout, "\ni.e. aplay -Dsof:::: file.wav\n"); + fprintf(stdout, "\nwhich can be used as\n"); + fprintf(stdout, "\ne.g. aplay -Dsof:bdw-nocodec:1:default:default:48k2c16b -f dat ~/audiodump.wav\n\n"); + + /* create context */ + plug = calloc(1, sizeof(*plug)); + if (!plug) + return -ENOMEM; + + pcm = calloc(1, sizeof(*pcm)); + if (!pcm) { + free(plug); + return -ENOMEM; + } + plug->module_prv = pcm; + + if (stream == SND_PCM_STREAM_CAPTURE) + pcm->capture = 1; + + /* parse the ALSA configuration file for sof plugin */ + err = plug_parse_conf(plug, name, root, conf, false); + if (err < 0) { + SNDERR("failed to parse config: %s", strerror(err)); + goto parse_conf_err; + } + + /* now try and connect to the sof-pipe for this topology */ + err = plug_init_sof_pipe(plug, pcmp, name, stream, mode); + if (err < 0) { + SNDERR("failed to complete plugin init: %s", strerror(err)); + goto pipe_error; + } + + /* everything is good */ + return 0; + +pipe_error: + free(plug->tplg_file); +parse_conf_err: + free(plug->module_prv); +dev_error: + free(plug); + return err; +} + +SND_PCM_PLUGIN_SYMBOL(sof); diff --git a/tools/plugin/alsaplug/plugin.c b/tools/plugin/alsaplug/plugin.c new file mode 100644 index 000000000000..52bbdcfc70e1 --- /dev/null +++ b/tools/plugin/alsaplug/plugin.c @@ -0,0 +1,361 @@ +/*-*- linux-c -*-*/ + +/* + * ALSA <-> SOF PCM I/O plugin + * + * Copyright(c) 2022 Intel Corporation. All rights reserved. + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "plugin.h" +#include "common.h" + +/* + * Open an existing semaphore using lock object. + */ +int plug_lock_open(struct plug_sem_desc *lock) +{ + lock->sem = sem_open(lock->name, O_RDWR); + if (lock->sem == SEM_FAILED) { + SNDERR("failed to open semaphore %s: %s\n", lock->name, strerror(errno)); + return -errno; + } + + return 0; +} + +#define itemsize(type, member) sizeof(((type *)0)->member) + +static int parse_conf_long(snd_config_t *cfg, void *obj, size_t size) +{ + long val; + + if (snd_config_get_integer(cfg, &val) < 0) + return -EINVAL; + + *((long *)obj) = val; + return 0; +} + +static int parse_conf_str(snd_config_t *cfg, void *obj, size_t size) +{ + const char *id; + + if (snd_config_get_id(cfg, &id) < 0) + return -EINVAL; + strncpy(obj, id, size); + + return 0; +} + +static int parse_conf_format(snd_config_t *cfg, void *obj, size_t size) +{ + const char *id; + + if (snd_config_get_string(cfg, &id) < 0) + return -EINVAL; + + if (!strcmp("S16_LE", id)) { + *((long *)obj) = SND_PCM_FORMAT_S16_LE; + return 0; + } + if (!strcmp("S32_LE", id)) { + *((long *)obj) = SND_PCM_FORMAT_S32_LE; + return 0; + } + if (!strcmp("S24_4LE", id)) { + *((long *)obj) = SND_PCM_FORMAT_S24_LE; + return 0; + } + if (!strcmp("FLOAT", id)) { + *((long *)obj) = SND_PCM_FORMAT_FLOAT_LE; + return 0; + } + + /* not found */ + SNDERR("error: cant find format: %s", id); + return -EINVAL; +} + +struct config_item { + char *name; + size_t size; + size_t offset; + int (*copy)(snd_config_t *cfg, void *obj, size_t size); +}; + +struct config_item config_items[] = { + {"name", itemsize(struct plug_config, name), + offsetof(struct plug_config, name), parse_conf_str}, + {"rate", itemsize(struct plug_config, rate), + offsetof(struct plug_config, rate), parse_conf_long}, + {"format", itemsize(struct plug_config, format), + offsetof(struct plug_config, format), parse_conf_format}, + {"channels", itemsize(struct plug_config, channels), + offsetof(struct plug_config, channels), parse_conf_long}, + {"period_time", itemsize(struct plug_config, period_time), + offsetof(struct plug_config, period_time), parse_conf_long}, + {"period_frames", itemsize(struct plug_config, period_frames), + offsetof(struct plug_config, period_frames), parse_conf_long}, + {"buffer_time", itemsize(struct plug_config, buffer_time), + offsetof(struct plug_config, buffer_time), parse_conf_long}, + {"buffer_frames", itemsize(struct plug_config, buffer_frames), + offsetof(struct plug_config, buffer_frames), parse_conf_long}, +}; + +static int parse_item(snd_config_t *cfg, const char *id, struct plug_config *dest_cfg) +{ + void *dest = dest_cfg; + int i; + + for (i = 0; i < ARRAY_SIZE(config_items); i++) { + /* does ID match */ + if (strcmp(id, config_items[i].name)) + continue; + + /* now get the value */ + return config_items[i].copy(cfg, dest + config_items[i].offset, + config_items[i].size); + } + + /* not found - non fatal */ + return 0; +} + +static int parse_slave_configs(snd_sof_plug_t *plug, snd_config_t *n) +{ + snd_config_iterator_t si1, si2, snext1, snext2; + struct plug_config *config; + const char *id; + + fprintf(stdout, "Parsing ALSA conf for configs\n"); + + snd_config_for_each(si1, snext1, n) { + snd_config_t *sn1 = snd_config_iterator_entry(si1); + + config = &plug->config[plug->num_configs]; + + /* get config name */ + if (parse_item(sn1, "name", config) < 0) { + SNDERR("error: cant find config name"); + return -EINVAL; + } + + /* now get item values in each config */ + snd_config_for_each(si2, snext2, sn1) { + snd_config_t *sn2 = snd_config_iterator_entry(si2); + + if (snd_config_get_id(sn2, &id) < 0) + continue; + + if (parse_item(sn2, id, config) < 0) { + SNDERR("error: malformed config: %s", id); + return -EINVAL; + } + } + + fprintf(stdout, " config %d: %s\n", plug->num_configs, + config->name); + + /* next config */ + plug->num_configs++; + if (plug->num_configs >= PLUG_MAX_CONFIG) { + SNDERR("error: too many configs"); + return -EINVAL; + } + } + + return 0; +} + +/* + * Parse the client cmdline. Format is + * tplg:pcm:card:dev:config[dai_pipe:card:dev:config]...] + */ +static int parse_client_cmdline(snd_sof_plug_t *plug, char *cmdline, bool just_tplg) +{ + struct plug_cmdline_item *cmd_item; + char *tplg, *next, *card, *dev, *config, *pcm; + char *tplg_path = getenv("SOF_PLUGIN_TOPOLOGY_PATH"); + char tplg_file[128]; + int ret; + int i; + + if (!tplg_path) { + SNDERR("Invalid topology path. Please set the SOF_PLUGIN_TOPOLOGY_PATH env variable\n"); + return -EINVAL; + } + + /* get topology file */ + tplg = strtok_r(cmdline, ":", &next); + if (!tplg) { + SNDERR("invalid cmdline, cant find topology %s", cmdline); + return -EINVAL; + } + + /* now convert to filename and add the topology path */ + ret = snprintf(tplg_file, sizeof(tplg_file), "%ssof-%s.tplg", tplg_path, tplg); + if (ret < 0) { + SNDERR("invalid cmdline topology file %s", tplg); + return -EINVAL; + } + plug->tplg_file = strdup(tplg_file); + if (!plug->tplg_file) + return -ENOMEM; + + if (just_tplg) + return 0; + + /* get PCM ID */ + pcm = strtok_r(next, ":", &next); + if (!pcm) { + SNDERR("invalid cmdline, cant find PCM %s", pcm); + return -EINVAL; + } + plug->pcm_id = atoi(pcm); + + fprintf(stdout, "Parsing cmd line\n"); + + cmd_item = &plug->cmdline[plug->num_cmdline]; + card = strtok_r(next, ":", &next); + /* card/dev names and config are all optional */ + if (!card) { + strncpy(cmd_item->card_name, "default", sizeof(cmd_item->card_name)); + strncpy(cmd_item->dev_name, "default", sizeof(cmd_item->dev_name)); + fprintf(stdout, "no config name provided, will use hw_params\n"); + } else { + strncpy(cmd_item->card_name, card, sizeof(cmd_item->card_name)); + dev = strtok_r(next, ":", &next); + /* dev name must be provided along with card name */ + if (!dev) { + SNDERR("Invalid dev name\n"); + return -EINVAL; + } + strncpy(cmd_item->dev_name, dev, sizeof(cmd_item->dev_name)); + config = strtok_r(next, ":", &next); + /* config name is optional */ + if (!config) + fprintf(stdout, "no config name provided, will use hw_params\n"); + else + strncpy(cmd_item->config_name, config, sizeof(cmd_item->config_name)); + } + + cmd_item->pcm = atoi(pcm); + + /* + * dev name is special, we cant use "," in the command line + * so need to replace it with a "." and then later change it + * back to "," + */ + for (i = 0; i < sizeof(cmd_item->dev_name); i++) { + if (cmd_item->dev_name[i] != '.') + continue; + cmd_item->dev_name[i] = ','; + break; + } + + fprintf(stdout, " cmd %d: for pcm %d uses %s with PCM %s:%s\n", + plug->num_cmdline, cmd_item->pcm, cmd_item->config_name, + cmd_item->card_name, cmd_item->dev_name); + + plug->num_cmdline++; + + printf("plug: topology file %s with pipe %ld\n", plug->tplg_file, plug->tplg_pipeline); + return 0; +} + +/* + * Parse the ALSA conf for the SOF plugin and construct the command line options + * to be passed into the SOF pipe executable. + * TODO: verify all args + * TODO: validate all args. + * TODO: contruct sof pipe cmd line. + */ +int plug_parse_conf(snd_sof_plug_t *plug, const char *name, snd_config_t *root, + snd_config_t *conf, bool just_tplg) +{ + snd_config_iterator_t i, next; + const char *tplg = NULL; + + /* + * The topology filename and topology PCM need to be passed in. + * i.e. aplay -Dsof:tplg:pcm:[card:dev:config]...] + */ + snd_config_for_each(i, next, conf) { + snd_config_t *n = snd_config_iterator_entry(i); + const char *id; + + if (snd_config_get_id(n, &id) < 0) + continue; + + /* dont care */ + if (strcmp(id, "comment") == 0 || strcmp(id, "type") == 0 || + strcmp(id, "hint") == 0) + continue; + + /* client command line topology */ + if (strcmp(id, "tplg") == 0) { + if (snd_config_get_string(n, &tplg) < 0) { + SNDERR("Invalid type for %s", id); + return -EINVAL; + } else if (!*tplg) { + tplg = NULL; + } + continue; + } + + /* topology PCM configurations */ + if (strcmp(id, "config") == 0) { + if (parse_slave_configs(plug, n)) + return -EINVAL; + continue; + } + + /* not fatal - carry on and verify later */ + SNDERR("Unknown field %s", id); + } + + /* verify mandatory inputs are specified */ + if (!tplg) { + SNDERR("Missing topology topology"); + return -EINVAL; + } + + /* parse the client command line */ + if (parse_client_cmdline(plug, (char *)tplg, just_tplg)) { + SNDERR("invalid sof cmd line"); + return -EINVAL; + } + + return 0; +} diff --git a/tools/plugin/alsaplug/plugin.h b/tools/plugin/alsaplug/plugin.h new file mode 100644 index 000000000000..7f4009267d24 --- /dev/null +++ b/tools/plugin/alsaplug/plugin.h @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2022-2023 Intel Corporation. All rights reserved. + * + * Author: Liam Girdwood + */ + +#ifndef __SOF_PLUGIN_PLUGIN_H__ +#define __SOF_PLUGIN_PLUGIN_H__ + +#include +#include +#include "common.h" + +#include + +#define PLUG_MAX_CONFIG 128 + +typedef struct snd_sof_plug { + /* conf data */ + char *device; + + /* topology info */ + char *tplg_file; + long tplg_pipeline; // HACK, use configs + + /* number of configurations in plugin conf */ + struct plug_config config[PLUG_MAX_CONFIG]; + int num_configs; + + /* command line arguments */ + struct plug_cmdline_item cmdline[PLUG_MAX_CONFIG]; + int num_cmdline; + + /* 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 plug_socket_desc ipc; + + struct plug_shm_desc glb_ctx; + + int pcm_id; + struct tplg_pcm_info *pcm_info; + + snd_pcm_uframes_t period_size; + + void *module_prv; /* module private data */ +} snd_sof_plug_t; + +/* + * ALSA Conf + */ +int sofplug_load_hook(snd_config_t *root, snd_config_t *config, + snd_config_t **dst, snd_config_t *private_data); + +int plug_parse_conf(snd_sof_plug_t *plug, const char *name, + snd_config_t *root, snd_config_t *conf, bool just_tplg); +int plug_parse_topology(snd_sof_plug_t *plug); +int plug_set_up_pipelines(snd_sof_plug_t *plug, int dir, snd_pcm_hw_params_t *params); +int plug_free_pipelines(snd_sof_plug_t *plug, struct tplg_pipeline_list *pipeline_list, int dir); +void plug_free_topology(snd_sof_plug_t *plug); +int plug_kcontrol_cb_new(struct snd_soc_tplg_ctl_hdr *tplg_ctl, void *_comp, void *arg, int index); + +#endif diff --git a/tools/plugin/alsaplug/tplg.c b/tools/plugin/alsaplug/tplg.c new file mode 100644 index 000000000000..7b05b7ead50a --- /dev/null +++ b/tools/plugin/alsaplug/tplg.c @@ -0,0 +1,1554 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2018 Intel Corporation. All rights reserved. +// +// Author: Ranjani Sridharan +// Liam Girdwood + +/* Topology loader to set up components and pipeline */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include + +#include "plugin.h" + +#define FILE_READ 0 +#define FILE_WRITE 1 + +#define MAX_TPLG_OBJECT_SIZE 4096 + +//#include + +/* temporary - current MAXLEN is not define in UAPI header - fix pending */ +#ifndef SNDRV_CTL_ELEM_ID_NAME_MAXLEN +#define SNDRV_CTL_ELEM_ID_NAME_MAXLEN 44 +#endif +#include + +#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)}, +}; + +static int plug_parse_ipc4_comp_tokens(snd_sof_plug_t *plug, struct ipc4_base_module_cfg *base_cfg) +{ + struct tplg_context *ctx = &plug->tplg; + struct tplg_comp_info *comp_info = ctx->current_comp_info; + struct snd_soc_tplg_vendor_array *array = &ctx->widget->priv.array[0]; + int size = ctx->widget->priv.size; + int ret; + + ret = sof_parse_token_sets(base_cfg, ipc4_comp_tokens, ARRAY_SIZE(ipc4_comp_tokens), + array, size, 1, 0); + if (ret < 0) + return ret; + + return sof_parse_tokens(&comp_info->uuid, comp_ext_tokens, + ARRAY_SIZE(comp_ext_tokens), array, size); +} + +static void plug_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; +} + +static int plug_aif_in_out(snd_sof_plug_t *plug, int dir) +{ + struct tplg_context *ctx = &plug->tplg; + struct tplg_comp_info *comp_info = ctx->current_comp_info; + int ret; + + ret = tplg_parse_widget_audio_formats(ctx); + if (ret < 0) + return ret; + + comp_info->ipc_size = sizeof(struct ipc4_base_module_cfg) + sizeof(struct sof_uuid); + comp_info->ipc_payload = calloc(comp_info->ipc_size, 1); + if (!comp_info->ipc_payload) + return -ENOMEM; + + /* overwrite the topology UUIDs with the SHM module UUID for the host components */ + if (dir == SOF_IPC_STREAM_PLAYBACK) { + struct sof_uuid uuid = {.a = 0xe2b6031c, .b = 0x47e8, .c = 0x11ed, + .d = { 0x07, 0xa9, 0x7f, 0x80, 0x1b, 0x6e, 0xfa, 0x6c }}; + comp_info->module_id = 0x96; + plug_setup_widget_ipc_msg(comp_info); + comp_info->uuid = uuid; + } else { + struct sof_uuid uuid = {.a = 0xdabe8814, .b = 0x47e8, .c = 0x11ed, + .d = { 0xa5, 0x8b, 0xb3, 0x09, 0x97, 0x4f, 0xec, 0xce }}; + comp_info->module_id = 0x98; + plug_setup_widget_ipc_msg(comp_info); + comp_info->uuid = uuid; + } + + /* copy uuid to the end of the payload */ + memcpy(comp_info->ipc_payload + sizeof(struct ipc4_base_module_cfg), &comp_info->uuid, + sizeof(struct sof_uuid)); + + return 0; +} + +static int plug_dai_in_out(snd_sof_plug_t *plug, int dir) +{ + struct tplg_context *ctx = &plug->tplg; + struct tplg_comp_info *comp_info = ctx->current_comp_info; + int ret; + + ret = tplg_parse_widget_audio_formats(ctx); + if (ret < 0) + return ret; + + comp_info->ipc_size = sizeof(struct ipc4_base_module_cfg) + sizeof(struct sof_uuid); + comp_info->ipc_payload = calloc(comp_info->ipc_size, 1); + if (!comp_info->ipc_payload) + return -ENOMEM; + + /* overwrite the topology UUIDs with the ALSA module UUID for the DAI components */ + if (dir == SOF_IPC_STREAM_PLAYBACK) { + struct sof_uuid uuid = {.a = 0x72cee996, .b = 0x39f2, .c = 0x11ed, + .d = { 0xa0, 0x8f, 0x97, 0xfc, 0xc4, 0x2e, 0xaa, 0xeb }}; + + comp_info->module_id = 0x97; + plug_setup_widget_ipc_msg(comp_info); + comp_info->uuid = uuid; + } else { + struct sof_uuid uuid = {.a = 0x66def9f0, .b = 0x39f2, .c = 0x11ed, + .d = { 0xf7, 0x89, 0xaf, 0x98, 0xa6, 0x44, 0x0c, 0xc4 }}; + + comp_info->module_id = 0x99; + plug_setup_widget_ipc_msg(comp_info); + comp_info->uuid = uuid; + } + + /* copy uuid to the end of the payload */ + memcpy(comp_info->ipc_payload + sizeof(struct ipc4_base_module_cfg), &comp_info->uuid, + sizeof(struct sof_uuid)); + + return 0; +} + +static int plug_new_src_ipc(snd_sof_plug_t *plug) +{ + struct tplg_context *ctx = &plug->tplg; + char tplg_object[MAX_TPLG_OBJECT_SIZE] = {0}; + struct sof_ipc_comp_src *src = + (struct sof_ipc_comp_src *)tplg_object; + struct snd_soc_tplg_ctl_hdr *tplg_ctl; + int ret; + + tplg_ctl = calloc(ctx->hdr->payload_size, 1); + if (!tplg_ctl) + return -ENOMEM; + + ret = tplg_new_src(ctx, &src->comp, MAX_TPLG_OBJECT_SIZE, + tplg_ctl, ctx->hdr->payload_size); + if (ret < 0) { + SNDERR("error: failed to create src\n"); + goto out; + } + +out: + free(tplg_ctl); + return ret; +} + +static int plug_new_asrc_ipc(snd_sof_plug_t *plug) +{ + struct tplg_context *ctx = &plug->tplg; + char tplg_object[MAX_TPLG_OBJECT_SIZE] = {0}; + struct sof_ipc_comp_asrc *asrc = + (struct sof_ipc_comp_asrc *)tplg_object; + struct snd_soc_tplg_ctl_hdr *tplg_ctl; + int ret; + + tplg_ctl = calloc(ctx->hdr->payload_size, 1); + if (!tplg_ctl) + return -ENOMEM; + + ret = tplg_new_asrc(ctx, &asrc->comp, MAX_TPLG_OBJECT_SIZE, + tplg_ctl, ctx->hdr->payload_size); + if (ret < 0) { + SNDERR("error: failed to create PGA\n"); + goto out; + } + +out: + free(tplg_ctl); + return ret; +} + +static int plug_new_mixer(snd_sof_plug_t *plug) +{ + struct tplg_context *ctx = &plug->tplg; + struct tplg_comp_info *comp_info = ctx->current_comp_info; + char tplg_object[MAX_TPLG_OBJECT_SIZE] = {0}; + struct sof_ipc_comp_mixer *mixer = + (struct sof_ipc_comp_mixer *)tplg_object; + struct snd_soc_tplg_ctl_hdr *tplg_ctl; + int ret; + + tplg_ctl = calloc(ctx->hdr->payload_size, 1); + if (!tplg_ctl) + return -ENOMEM; + + comp_info->instance_id = plug->instance_ids[SND_SOC_TPLG_DAPM_MIXER]++; + comp_info->ipc_size = sizeof(struct ipc4_base_module_cfg) + sizeof(struct sof_uuid); + 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) { + SNDERR("error: failed to create mixer\n"); + goto out; + } + + if (strstr(comp_info->name, "mixin")) { + comp_info->module_id = 0x2; + plug_setup_widget_ipc_msg(comp_info); + } else { + comp_info->module_id = 0x3; + plug_setup_widget_ipc_msg(comp_info); + } + + /* copy uuid to the end of the payload */ + memcpy(comp_info->ipc_payload + sizeof(struct ipc4_base_module_cfg), &comp_info->uuid, + sizeof(struct sof_uuid)); +out: + free(tplg_ctl); + return ret; +} + +static int plug_new_pga(snd_sof_plug_t *plug) +{ + struct tplg_context *ctx = &plug->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; + uint32_t uuid_offset; + int ret; + + comp_info->ipc_size = sizeof(struct ipc4_peak_volume_config); + comp_info->ipc_size += sizeof(struct ipc4_base_module_cfg); + uuid_offset = comp_info->ipc_size; + comp_info->ipc_size += sizeof(struct sof_uuid); + 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 = plug->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) { + SNDERR("%s: failed to create PGA\n", __func__); + 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)); + + /* copy uuid to the end of the payload */ + memcpy(comp_info->ipc_payload + uuid_offset, &comp_info->uuid, sizeof(struct sof_uuid)); + + /* skip kcontrols for now */ + if (tplg_create_controls(ctx, ctx->widget->num_kcontrols, + tplg_ctl, ctx->hdr->payload_size, comp_info) < 0) { + SNDERR("error: loading controls\n"); + goto out; + } + + plug_setup_widget_ipc_msg(comp_info); + + free(tplg_ctl); + + return ret; + +out: + free(tplg_ctl); + free(comp_info->ipc_payload); + + return ret; +} + +static int plug_new_process(snd_sof_plug_t *plug) +{ + struct tplg_context *ctx = &plug->tplg; + struct tplg_comp_info *comp_info = ctx->current_comp_info; + struct ipc4_base_module_cfg basecfg; + struct snd_soc_tplg_ctl_hdr *tplg_ctl; + int ret; + + ret = tplg_parse_widget_audio_formats(ctx); + if (ret < 0) + return ret; + + /* only base config supported for now. extn support will be added later */ + comp_info->ipc_size = sizeof(struct ipc4_base_module_cfg) + sizeof(struct sof_uuid); + 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 = plug->instance_ids[SND_SOC_TPLG_DAPM_EFFECT]++; + comp_info->module_id = 0x95; + + plug_setup_widget_ipc_msg(comp_info); + + tplg_ctl = calloc(ctx->hdr->payload_size, 1); + if (!tplg_ctl) { + free(comp_info->ipc_payload); + return -ENOMEM; + } + + /* copy uuid to the end of the payload */ + memcpy(comp_info->ipc_payload + sizeof(struct ipc4_base_module_cfg), &comp_info->uuid, + sizeof(struct sof_uuid)); + + /* set up kcontrols */ + ret = tplg_create_controls(ctx, ctx->widget->num_kcontrols, + tplg_ctl, ctx->hdr->payload_size, comp_info); + if (ret < 0) { + SNDERR("failed to create controls for process comp\n"); + free(comp_info->ipc_payload); + } + + free(tplg_ctl); + return ret; +} + +static int plug_new_pipeline(snd_sof_plug_t *plug) +{ + 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 = &plug->tplg; + int ret; + + tplg_ctl = calloc(ctx->hdr->payload_size, 1); + if (!tplg_ctl) + return -ENOMEM; + + pipe_info = calloc(sizeof(struct tplg_pipeline_info), 1); + if (!pipe_info) { + ret = -ENOMEM; + goto out; + } + + pipe_info->name = strdup(ctx->widget->name); + if (!pipe_info->name) { + free(pipe_info); + goto out; + } + + pipe_info->id = ctx->pipeline_id; + + ret = tplg_new_pipeline(ctx, &pipeline, sizeof(pipeline), tplg_ctl); + if (ret < 0) { + SNDERR("error: failed to create pipeline\n"); + free(pipe_info->name); + free(pipe_info); + goto out; + } + + list_item_append(&pipe_info->item, &plug->pipeline_list); + tplg_debug("loading pipeline %s\n", pipe_info->name); +out: + free(tplg_ctl); + return ret; +} + +static int plug_new_buffer(snd_sof_plug_t *plug) +{ + struct ipc4_copier_module_cfg *copier = calloc(sizeof(struct ipc4_copier_module_cfg), 1); + struct tplg_context *ctx = &plug->tplg; + struct tplg_comp_info *comp_info = ctx->current_comp_info; + int ret; + + if (!copier) + return -ENOMEM; + + comp_info->ipc_payload = copier; + + ret = tplg_new_buffer(ctx, copier, sizeof(copier), NULL, 0); + if (ret < 0) { + SNDERR("error: failed to create pipeline\n"); + free(copier); + } + + return ret; +} + +/* Insert new comp info into the list of widgets */ +static inline int plug_insert_comp(snd_sof_plug_t *plug) +{ + struct tplg_context *ctx = &plug->tplg; + struct tplg_comp_info *comp_info; + int comp_id = ctx->comp_id; + int ret; + + if (ctx->widget->id == SND_SOC_TPLG_DAPM_SCHEDULER) + return 0; + + comp_info = calloc(sizeof(struct tplg_comp_info), 1); + if (!comp_info) + return -ENOMEM; + + comp_info->name = strdup(ctx->widget->name); + if (!comp_info->name) { + ret = -ENOMEM; + goto err; + } + + comp_info->stream_name = strdup(ctx->widget->sname); + if (!comp_info->stream_name) { + ret = -ENOMEM; + goto sname_err; + } + + comp_info->id = comp_id; + comp_info->type = ctx->widget->id; + comp_info->pipeline_id = ctx->pipeline_id; + ctx->current_comp_info = comp_info; + + ret = plug_parse_ipc4_comp_tokens(plug, &comp_info->basecfg); + if (ret < 0) + goto sname_err; + + list_item_append(&comp_info->item, &plug->widget_list); + + tplg_debug("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 plug_load_widget(snd_sof_plug_t *plug) +{ + struct tplg_context *ctx = &plug->tplg; + int ret = 0; + + /* get next widget */ + ctx->widget = tplg_get_widget(ctx); + ctx->widget_size = ctx->widget->size; + + /* insert widget into mapping */ + ret = plug_insert_comp(plug); + if (ret < 0) { + SNDERR("plug_load_widget: invalid widget index\n"); + return ret; + } + + /* load widget based on type */ + switch (ctx->widget->id) { + /* load pga widget */ + case SND_SOC_TPLG_DAPM_PGA: + if (plug_new_pga(plug) < 0) { + SNDERR("error: load pga\n"); + ret = -EINVAL; + goto exit; + } + break; + case SND_SOC_TPLG_DAPM_AIF_IN: + if (plug_aif_in_out(plug, SOF_IPC_STREAM_PLAYBACK) < 0) { + SNDERR("error: load AIF IN failed\n"); + ret = -EINVAL; + goto exit; + } + break; + case SND_SOC_TPLG_DAPM_AIF_OUT: + if (plug_aif_in_out(plug, SOF_IPC_STREAM_CAPTURE) < 0) { + SNDERR("error: load AIF OUT failed\n"); + ret = -EINVAL; + goto exit; + } + break; + case SND_SOC_TPLG_DAPM_DAI_IN: + if (plug_dai_in_out(plug, SOF_IPC_STREAM_PLAYBACK) < 0) { + SNDERR("error: load filewrite\n"); + ret = -EINVAL; + goto exit; + } + break; + case SND_SOC_TPLG_DAPM_DAI_OUT: + if (plug_dai_in_out(plug, SOF_IPC_STREAM_CAPTURE) < 0) { + SNDERR("error: load filewrite\n"); + ret = -EINVAL; + goto exit; + } + break; + + case SND_SOC_TPLG_DAPM_BUFFER: + if (plug_new_buffer(plug) < 0) { + SNDERR("error: load pipeline\n"); + ret = -EINVAL; + goto exit; + } + break; + + case SND_SOC_TPLG_DAPM_SCHEDULER: + if (plug_new_pipeline(plug) < 0) { + SNDERR("error: load pipeline\n"); + ret = -EINVAL; + goto exit; + } + break; + case SND_SOC_TPLG_DAPM_SRC: + if (plug_new_src_ipc(plug) < 0) { + SNDERR("error: load src\n"); + ret = -EINVAL; + goto exit; + } + break; + case SND_SOC_TPLG_DAPM_ASRC: + if (plug_new_asrc_ipc(plug) < 0) { + SNDERR("error: load asrc\n"); + ret = -EINVAL; + goto exit; + } + break; + + case SND_SOC_TPLG_DAPM_MIXER: + if (plug_new_mixer(plug) < 0) { + SNDERR("error: load mixer\n"); + ret = -EINVAL; + goto exit; + } + break; + case SND_SOC_TPLG_DAPM_EFFECT: + if (plug_new_process(plug) < 0) { + SNDERR("error: load effect\n"); + ret = -EINVAL; + goto exit; + } + break; + + /* unsupported widgets */ + default: + tplg_debug("info: Widget %s id %d unsupported and skipped: size %d priv size %d\n", + ctx->widget->name, ctx->widget->id, + ctx->widget->size, ctx->widget->priv.size); + +#if 0 + if (fseek(ctx->file, ctx->widget->priv.size, SEEK_CUR)) { + SNDERR("error: fseek unsupported widget\n"); + ret = -errno; + goto exit; + } + ret = tplg_create_controls(ctx->widget->num_kcontrols, ctx->file, NULL, 0); + if (ret < 0) { + SNDERR("error: loading controls\n"); + //goto exit; + } +#endif + ret = 0; + break; + } + + ret = 1; + +exit: + return ret; +} + +static int plug_register_graph(snd_sof_plug_t *plug, int count) +{ + struct tplg_context *ctx = &plug->tplg; + int ret = 0; + int i; + + for (i = 0; i < count; i++) { + ret = tplg_parse_graph(ctx, &plug->widget_list, &plug->route_list); + if (ret < 0) + return ret; + } + + return ret; +} + +static int plug_parse_pcm(snd_sof_plug_t *plug, int count) +{ + struct tplg_context *ctx = &plug->tplg; + int ret, i; + + for (i = 0; i < count; i++) { + ret = tplg_parse_pcm(ctx, &plug->widget_list, &plug->pcm_list); + if (ret < 0) + return ret; + } + + return 0; +} + +static void +plug_pipeline_update_resource_usage(snd_sof_plug_t *plug, 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; +} + +static int plug_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; + + _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; +} + +static int plug_match_audio_format(snd_sof_plug_t *plug, struct tplg_comp_info *comp_info, + snd_pcm_hw_params_t *params) +{ + 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; + snd_pcm_format_t params_format; + unsigned int params_channels, params_rate, dir; + int params_valid_bits; + int i; + + snd_pcm_hw_params_get_channels(params, ¶ms_channels); + snd_pcm_hw_params_get_rate(params, ¶ms_rate, &dir); + snd_pcm_hw_params_get_format(params, ¶ms_format); + + switch (params_format) { + case SND_PCM_FORMAT_S16_LE: + params_valid_bits = 16; + break; + case SND_PCM_FORMAT_S32_LE: + params_valid_bits = 32; + break; + case SND_PCM_FORMAT_S24_LE: + params_valid_bits = 24; + break; + default: + break; + } + + if (plug_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 == params_rate && channels == params_channels && + valid_bits == params_valid_bits) + break; + } + + if (i == available_fmt->num_input_formats) { + SNDERR("Cannot find matching format for rate %d channels %d valid_bits %d for %s\n", + params_rate, params_channels, params_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; + + /* Choose ALSA period size for ibs/obs so that the buffer sizes will be set accordingly */ + base_cfg->ibs = plug->period_size * 2; + base_cfg->obs = plug->period_size * 2; + + return 0; +} + +static int plug_set_up_widget_base_config(snd_sof_plug_t *plug, struct tplg_comp_info *comp_info, + snd_pcm_hw_params_t *params) +{ + int ret, i; + + /* match audio formats and populate base config */ + ret = plug_match_audio_format(plug, comp_info, params); + 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; +} + +/* parse topology file and set up pipeline */ +int plug_parse_topology(snd_sof_plug_t *plug) + +{ + struct tplg_context *ctx = &plug->tplg; + struct snd_soc_tplg_hdr *hdr; + struct list_item *item; + char pipeline_string[256] = {0}; + int i; + int ret = 0; + FILE *file; + size_t size; + + tplg_debug("parsing topology file %s\n", ctx->tplg_file); + + ctx->ctl_arg = plug; + ctx->ctl_cb = plug_kcontrol_cb_new; + + /* open topology file */ + file = fopen(ctx->tplg_file, "rb"); + if (!file) { + SNDERR("error: can't open topology %s : %s\n", ctx->tplg_file, strerror(errno)); + return -errno; + } + + /* file size */ + if (fseek(file, 0, SEEK_END)) { + SNDERR("error: can't seek to end of topology: %s\n", strerror(errno)); + fclose(file); + return -errno; + } + ctx->tplg_size = ftell(file); + if (fseek(file, 0, SEEK_SET)) { + SNDERR("error: can't seek to beginning of topology: %s\n", strerror(errno)); + fclose(file); + return -errno; + } + + /* load whole topology into memory */ + ctx->tplg_base = calloc(ctx->tplg_size, 1); + if (!ctx->tplg_base) { + SNDERR("error: can't alloc buffer for topology %zu bytes\n", ctx->tplg_size); + fclose(file); + return -ENOMEM; + } + ret = fread(ctx->tplg_base, ctx->tplg_size, 1, file); + if (ret != 1) { + SNDERR("error: can't read topology: %s\n", + strerror(errno)); + fclose(file); + return -errno; + } + fclose(file); + + /* initialize widget, route, pipeline and pcm lists */ + list_init(&plug->widget_list); + list_init(&plug->route_list); + list_init(&plug->pcm_list); + list_init(&plug->pipeline_list); + + while (ctx->tplg_offset < ctx->tplg_size) { + /* read next topology header */ + hdr = tplg_get_hdr(ctx); + + tplg_debug("type: %x, size: 0x%x count: %d index: %d\n", + hdr->type, hdr->payload_size, hdr->count, hdr->index); + + ctx->hdr = hdr; + + /* parse header and load the next block based on type */ + switch (hdr->type) { + /* load dapm widget */ + case SND_SOC_TPLG_TYPE_DAPM_WIDGET: + tplg_debug("number of DAPM widgets %d\n", hdr->count); + + /* update max pipeline_id */ + ctx->pipeline_id = hdr->index; + + for (i = 0; i < hdr->count; i++) { + ret = plug_load_widget(plug); + if (ret < 0) { + SNDERR("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 (plug_register_graph(plug, hdr->count) < 0) { + SNDERR("error: pipeline graph\n"); + return -EINVAL; + } + break; + /* parse PCM info */ + case SND_SOC_TPLG_TYPE_PCM: + ret = plug_parse_pcm(plug, hdr->count); + if (ret < 0) + goto out; + break; + default: + tplg_debug("%s %d\n", __func__, __LINE__); + tplg_skip_hdr_payload(ctx); + break; + } + } + + /* assign pipeline to every widget in the widget list */ + list_for_item(item, &plug->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, &plug->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) { + SNDERR("Error assigning pipeline for %s\n", comp_info->name); + return -EINVAL; + } + } +out: + return ret; +} + +static int plug_set_up_widget_ipc(snd_sof_plug_t *plug, 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, ret; + + 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); + + ret = plug_ipc_cmd_tx_rx(&plug->ipc, msg, size, &reply, sizeof(reply)); + free(msg); + if (ret < 0) { + SNDERR("error: can't set up widget %s\n", comp_info->name); + return ret; + } + + if (reply.primary.r.status != IPC4_SUCCESS) { + SNDERR("widget %s set up failed with status %d\n", + comp_info->name, reply.primary.r.status); + return -EINVAL; + } + return 0; +} + +static int plug_set_up_pipeline(snd_sof_plug_t *plug, struct tplg_pipeline_info *pipe_info) +{ + struct ipc4_pipeline_create msg = {{ 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 = plug->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; + + ret = plug_ipc_cmd_tx_rx(&plug->ipc, &msg, sizeof(msg), &reply, sizeof(reply)); + if (ret < 0) { + SNDERR("error: can't set up pipeline %s\n", pipe_info->name); + return ret; + } + + if (reply.primary.r.status != IPC4_SUCCESS) { + SNDERR("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; +} + +static int plug_prepare_widget(snd_sof_plug_t *plug, struct tplg_pcm_info *pcm_info, + struct tplg_comp_info *comp_info, int dir, + snd_pcm_hw_params_t *params) +{ + 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 = plug_set_up_widget_base_config(plug, comp_info, params); + if (ret < 0) + return ret; + + plug_pipeline_update_resource_usage(plug, 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 plug_prepare_widgets(snd_sof_plug_t *plug, struct tplg_pcm_info *pcm_info, + struct tplg_comp_info *starting_comp_info, + struct tplg_comp_info *current_comp_info, + snd_pcm_hw_params_t *params) +{ + struct list_item *item; + int ret; + + /* for playback */ + list_for_item(item, &plug->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 = plug_prepare_widget(plug, pcm_info, current_comp_info, 0, params); + if (ret < 0) + return ret; + } + + /* set up the sink widget */ + ret = plug_prepare_widget(plug, pcm_info, route_info->sink, 0, params); + 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 = plug_prepare_widgets(plug, pcm_info, starting_comp_info, + route_info->sink, params); + if (ret < 0) + return ret; + } + } + + return 0; +} + +static int plug_prepare_widgets_capture(snd_sof_plug_t *plug, struct tplg_pcm_info *pcm_info, + struct tplg_comp_info *starting_comp_info, + struct tplg_comp_info *current_comp_info, + snd_pcm_hw_params_t *params) +{ + struct list_item *item; + int ret; + + /* for capture */ + list_for_item(item, &plug->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 = plug_prepare_widget(plug, pcm_info, current_comp_info, 1, params); + if (ret < 0) + return ret; + } + + /* set up the source widget */ + ret = plug_prepare_widget(plug, pcm_info, route_info->source, 1, params); + 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 = plug_prepare_widgets_capture(plug, pcm_info, starting_comp_info, + route_info->source, params); + if (ret < 0) + return ret; + } + } + + return 0; +} + +static int plug_set_up_route(snd_sof_plug_t *plug, 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 = plug_ipc_cmd_tx_rx(&plug->ipc, &bu, sizeof(bu), &reply, sizeof(reply)); + if (ret < 0) { + SNDERR("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) { + SNDERR("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; +} + +static int plug_set_up_widget(snd_sof_plug_t *plug, struct tplg_comp_info *comp_info) +{ + struct tplg_pipeline_info *pipe_info = comp_info->pipe_info; + struct plug_shm_glb_state *glb = plug->glb_ctx.addr; + struct plug_shm_ctl *ctl; + int ret, i; + + 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 = plug_set_up_pipeline(plug, pipe_info); + if (ret < 0) { + pipe_info->usage_count--; + return ret; + } + } + + /* now set up the widget */ + ret = plug_set_up_widget_ipc(plug, comp_info); + if (ret < 0) + return ret; + + /* send kcontrol bytes data */ + for (i = 0; i < glb->num_ctls; i++) { + struct snd_soc_tplg_bytes_control *tplg_bytes; + struct sof_abi_hdr *abi; + int priv_size; + + ctl = &glb->ctl[i]; + + /* send the bytes data from kcontrols associated with current widget */ + if (ctl->module_id != comp_info->module_id || + ctl->instance_id != comp_info->instance_id || + ctl->type != SND_SOC_TPLG_TYPE_BYTES) + continue; + + tplg_bytes = &ctl->bytes_ctl; + priv_size = tplg_bytes->priv.size; + abi = (struct sof_abi_hdr *)ctl->data; + + /* send IPC with kcontrol data */ + ret = plug_send_bytes_data(&plug->ipc, comp_info->module_id, + comp_info->instance_id, abi); + if (ret < 0) { + SNDERR("failed to set bytes data for widget %s\n", comp_info->name); + return ret; + } + } + + tplg_debug("widget %s set up\n", comp_info->name); + + return 0; +} + +static int plug_set_up_widgets(snd_sof_plug_t *plug, 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, &plug->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 = plug_set_up_widget(plug, current_comp_info); + if (ret < 0) + return ret; + } + + /* set up the sink widget */ + ret = plug_set_up_widget(plug, route_info->sink); + if (ret < 0) + return ret; + + /* source and sink widgets are up, so set up route now */ + ret = plug_set_up_route(plug, 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 = plug_set_up_widgets(plug, starting_comp_info, route_info->sink); + if (ret < 0) + return ret; + } + } + + return 0; +} + +static int plug_set_up_widgets_capture(snd_sof_plug_t *plug, + 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, &plug->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 = plug_set_up_widget(plug, current_comp_info); + if (ret < 0) + return ret; + } + + /* set up the sink widget */ + ret = plug_set_up_widget(plug, route_info->source); + if (ret < 0) + return ret; + + /* source and sink widgets are up, so set up route now */ + ret = plug_set_up_route(plug, 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 = plug_set_up_widgets_capture(plug, starting_comp_info, + route_info->source); + if (ret < 0) + return ret; + } + } + + return 0; +} + +int plug_set_up_pipelines(snd_sof_plug_t *plug, int dir, snd_pcm_hw_params_t *params) +{ + struct tplg_comp_info *host = NULL; + struct tplg_pcm_info *pcm_info; + struct list_item *item; + int ret; + + list_for_item(item, &plug->pcm_list) { + pcm_info = container_of(item, struct tplg_pcm_info, item); + + if (pcm_info->id == plug->pcm_id) { + if (dir) + host = pcm_info->capture_host; + else + host = pcm_info->playback_host; + break; + } + } + + if (!host) { + SNDERR("No host component found for PCM ID: %d\n", plug->pcm_id); + return -EINVAL; + } + + plug->pcm_info = pcm_info; + + if (dir) { + ret = plug_prepare_widgets_capture(plug, pcm_info, host, host, params); + if (ret < 0) + return ret; + + ret = plug_set_up_widgets_capture(plug, host, host); + if (ret < 0) + return ret; + + tplg_debug("Setting up capture pipelines complete\n"); + + return 0; + } + + ret = plug_prepare_widgets(plug, pcm_info, host, host, params); + if (ret < 0) + return ret; + + ret = plug_set_up_widgets(plug, host, host); + if (ret < 0) + return ret; + + tplg_debug("Setting up playback pipelines complete\n"); + + return 0; +} + +static int plug_delete_pipeline(snd_sof_plug_t *plug, 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 = plug_ipc_cmd_tx_rx(&plug->ipc, &msg, sizeof(msg), &reply, sizeof(reply)); + if (ret < 0) { + SNDERR("error: can't delete pipeline %s\n", pipe_info->name); + return ret; + } + + if (reply.primary.r.status != IPC4_SUCCESS) { + SNDERR("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; +} + +static int plug_free_route(snd_sof_plug_t *plug, 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 = plug_ipc_cmd_tx_rx(&plug->ipc, &bu, sizeof(bu), &reply, sizeof(reply)); + if (ret < 0) { + SNDERR("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) { + SNDERR("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; +} + +static int plug_free_widgets(snd_sof_plug_t *plug, 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, &plug->route_list) { + struct tplg_route_info *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 = plug_free_route(plug, 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 = plug_free_widgets(plug, starting_comp_info, route_info->sink); + if (ret < 0) + return ret; + } + } + + return 0; +} + +static int plug_free_widgets_capture(snd_sof_plug_t *plug, + 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, &plug->route_list) { + struct tplg_route_info *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 = plug_free_route(plug, 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 = plug_free_widgets_capture(plug, starting_comp_info, + route_info->source); + if (ret < 0) + return ret; + } + } + + return 0; +} + +int plug_free_pipelines(snd_sof_plug_t *plug, struct tplg_pipeline_list *pipeline_list, int dir) +{ + struct tplg_comp_info *host = NULL; + struct tplg_pcm_info *pcm_info; + struct list_item *item; + int ret, i; + + list_for_item(item, &plug->pcm_list) { + pcm_info = container_of(item, struct tplg_pcm_info, item); + + if (pcm_info->id == plug->pcm_id) { + if (dir) + host = pcm_info->capture_host; + else + host = pcm_info->playback_host; + break; + } + } + + if (!host) { + SNDERR("No host component found for PCM ID: %d\n", plug->pcm_id); + return -EINVAL; + } + + if (dir) { + ret = plug_free_widgets_capture(plug, host, host); + if (ret < 0) { + SNDERR("failed to free widgets for capture PCM %d\n", plug->pcm_id); + return ret; + } + } else { + ret = plug_free_widgets(plug, host, host); + if (ret < 0) { + SNDERR("failed to free widgets for PCM %d\n", plug->pcm_id); + return ret; + } + } + + for (i = 0; i < pipeline_list->count; i++) { + struct tplg_pipeline_info *pipe_info = pipeline_list->pipelines[i]; + + ret = plug_delete_pipeline(plug, pipe_info); + if (ret < 0) + return ret; + } + + plug->instance_ids[SND_SOC_TPLG_DAPM_SCHEDULER] = 0; + return 0; +} + +void plug_free_topology(snd_sof_plug_t *plug) +{ + struct tplg_context *ctx = &plug->tplg; + struct list_item *item, *_item; + + list_for_item_safe(item, _item, &plug->pcm_list) { + struct tplg_pcm_info *pcm_info = container_of(item, struct tplg_pcm_info, item); + + free(pcm_info->name); + free(pcm_info); + } + + list_for_item_safe(item, _item, &plug->widget_list) { + struct tplg_comp_info *comp_info = container_of(item, struct tplg_comp_info, item); + struct sof_ipc4_available_audio_format *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, &plug->route_list) { + struct tplg_route_info *route_info = container_of(item, struct tplg_route_info, + item); + + free(route_info); + } + + list_for_item_safe(item, _item, &plug->pipeline_list) { + struct tplg_pipeline_info *pipe_info = container_of(item, struct tplg_pipeline_info, + item); + + free(pipe_info->name); + free(pipe_info); + } + + free(ctx->tplg_base); + tplg_debug("freed all pipelines, widgets, routes and pcms\n"); +} diff --git a/tools/plugin/alsaplug/tplg_ctl.c b/tools/plugin/alsaplug/tplg_ctl.c new file mode 100644 index 000000000000..ad2709153806 --- /dev/null +++ b/tools/plugin/alsaplug/tplg_ctl.c @@ -0,0 +1,226 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2024 Intel Corporation. All rights reserved. +// + +/* SOF topology kcontrols */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include "plugin.h" +#include "common.h" + +#define SOF_IPC4_VOL_ZERO_DB 0x7fffffff +#define VOLUME_FWL 16 +/* + * Constants used in the computation of linear volume gain + * from dB gain 20th root of 10 in Q1.16 fixed-point notation + */ +#define VOL_TWENTIETH_ROOT_OF_TEN 73533 +/* 40th root of 10 in Q1.16 fixed-point notation*/ +#define VOL_FORTIETH_ROOT_OF_TEN 69419 + +/* 0.5 dB step value in topology TLV */ +#define VOL_HALF_DB_STEP 50 + +/* + * Function to truncate an unsigned 64-bit number + * by x bits and return 32-bit unsigned number. This + * function also takes care of rounding while truncating + */ +static uint32_t vol_shift_64(uint64_t i, uint32_t x) +{ + if (x == 0) + return (uint32_t)i; + + /* do not truncate more than 32 bits */ + if (x > 32) + x = 32; + + return (uint32_t)(((i >> (x - 1)) + 1) >> 1); +} + +/* + * Function to compute a ** exp where, + * a is a fractional number represented by a fixed-point integer with a fractional word length + * of "fwl" + * exp is an integer + * fwl is the fractional word length + * Return value is a fractional number represented by a fixed-point integer with a fractional + * word length of "fwl" + */ +static uint32_t vol_pow32(uint32_t a, int exp, uint32_t fwl) +{ + int i, iter; + uint32_t power = 1 << fwl; + unsigned long long numerator; + + /* if exponent is 0, return 1 */ + if (exp == 0) + return power; + + /* determine the number of iterations based on the exponent */ + if (exp < 0) + iter = exp * -1; + else + iter = exp; + + /* multiply a "iter" times to compute power */ + for (i = 0; i < iter; i++) { + /* + * Product of 2 Qx.fwl fixed-point numbers yields a Q2*x.2*fwl + * Truncate product back to fwl fractional bits with rounding + */ + power = vol_shift_64((uint64_t)power * a, fwl); + } + + if (exp > 0) { + /* if exp is positive, return the result */ + return power; + } + + /* if exp is negative, return the multiplicative inverse */ + numerator = (uint64_t)1 << (fwl << 1); + numerator /= power; + + return (uint32_t)numerator; +} + +/* + * Function to calculate volume gain from TLV data. + * This function can only handle gain steps that are multiples of 0.5 dB + */ +static uint32_t vol_compute_gain(uint32_t value, struct snd_soc_tplg_tlv_dbscale *scale) +{ + int dB_gain; + uint32_t linear_gain; + int f_step; + + /* mute volume */ + if (value == 0 && scale->mute) + return 0; + + /* compute dB gain from tlv. tlv_step in topology is multiplied by 100 */ + dB_gain = (int)scale->min / 100 + (value * scale->step) / 100; + + /* compute linear gain represented by fixed-point int with VOLUME_FWL fractional bits */ + linear_gain = vol_pow32(VOL_TWENTIETH_ROOT_OF_TEN, dB_gain, VOLUME_FWL); + + /* extract the fractional part of volume step */ + f_step = scale->step - (scale->step / 100); + + /* if volume step is an odd multiple of 0.5 dB */ + if (f_step == VOL_HALF_DB_STEP && (value & 1)) + linear_gain = vol_shift_64((uint64_t)linear_gain * VOL_FORTIETH_ROOT_OF_TEN, + VOLUME_FWL); + + return linear_gain; +} + +/* helper function to add new kcontrols to the list of kcontrols in the global context */ +int plug_kcontrol_cb_new(struct snd_soc_tplg_ctl_hdr *tplg_ctl, void *_comp, void *arg, int index) +{ + struct tplg_comp_info *comp_info = _comp; + snd_sof_plug_t *plug = arg; + struct plug_shm_glb_state *glb = plug->glb_ctx.addr; + struct plug_shm_ctl *ctl; + + if (glb->num_ctls >= MAX_CTLS) { + SNDERR("Failed to add a new control. Too many controls already\n"); + return -EINVAL; + } + + switch (tplg_ctl->ops.info) { + case SND_SOC_TPLG_CTL_VOLSW: + case SND_SOC_TPLG_CTL_VOLSW_SX: + case SND_SOC_TPLG_CTL_VOLSW_XR_SX: + { + struct snd_soc_tplg_mixer_control *tplg_mixer = + (struct snd_soc_tplg_mixer_control *)tplg_ctl; + struct snd_soc_tplg_ctl_tlv *tlv; + struct snd_soc_tplg_tlv_dbscale *scale; + int i; + + glb->size += sizeof(struct plug_shm_ctl); + ctl = &glb->ctl[glb->num_ctls++]; + ctl->module_id = comp_info->module_id; + ctl->instance_id = comp_info->instance_id; + ctl->mixer_ctl = *tplg_mixer; + ctl->index = index; + ctl->type = tplg_ctl->type; + tlv = &tplg_ctl->tlv; + scale = &tlv->scale; + + /* populate the volume table */ + for (i = 0; i < tplg_mixer->max + 1 ; i++) { + uint32_t val = vol_compute_gain(i, scale); + + /* Can be over Q1.31, need to saturate */ + uint64_t q31val = ((uint64_t)val) << 15; + + ctl->volume_table[i] = q31val > SOF_IPC4_VOL_ZERO_DB ? + SOF_IPC4_VOL_ZERO_DB : q31val; + } + break; + } + case SND_SOC_TPLG_CTL_ENUM: + case SND_SOC_TPLG_CTL_ENUM_VALUE: + { + struct snd_soc_tplg_enum_control *tplg_enum = + (struct snd_soc_tplg_enum_control *)tplg_ctl; + + glb->size += sizeof(struct plug_shm_ctl); + ctl = &glb->ctl[glb->num_ctls++]; + ctl->module_id = comp_info->module_id; + ctl->instance_id = comp_info->instance_id; + ctl->enum_ctl = *tplg_enum; + ctl->index = index; + ctl->type = tplg_ctl->type; + break; + } + case SND_SOC_TPLG_CTL_BYTES: + { + struct snd_soc_tplg_bytes_control *tplg_bytes = + (struct snd_soc_tplg_bytes_control *)tplg_ctl; + + glb->size += sizeof(struct plug_shm_ctl); + ctl = &glb->ctl[glb->num_ctls++]; + ctl->module_id = comp_info->module_id; + ctl->instance_id = comp_info->instance_id; + ctl->bytes_ctl = *tplg_bytes; + ctl->index = index; + ctl->type = tplg_ctl->type; + memcpy(ctl->data, tplg_bytes->priv.data, tplg_bytes->priv.size); + break; + } + case SND_SOC_TPLG_CTL_RANGE: + case SND_SOC_TPLG_CTL_STROBE: + default: + SNDERR("Invalid ctl type %d\n", tplg_ctl->type); + return -EINVAL; + } + + return 0; +} diff --git a/tools/plugin/common.c b/tools/plugin/common.c new file mode 100644 index 000000000000..f20cb14e0b0e --- /dev/null +++ b/tools/plugin/common.c @@ -0,0 +1,466 @@ +/*-*- linux-c -*-*/ + +/* + * ALSA <-> SOF PCM I/O plugin + * + * Copyright (c) 2022 by Liam Girdwood + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" + +/* + * Timing + */ +void plug_timespec_add_ms(struct timespec *ts, unsigned long ms) +{ + long ns; + long secs = ms / 1000; + + /* get ms remainder */ + ms = ms - (secs * 1000); + ns = ms * 1000000; + + ts->tv_nsec += ns; + if (ts->tv_nsec > 1000000000) { + secs++; + ts->tv_nsec -= 1000000000; + } + ts->tv_sec += (secs + DEBUG_TV_SECS); +} + +long plug_timespec_delta_ns(struct timespec *before, struct timespec *after) +{ + long ns; + + ns = (after->tv_sec - before->tv_sec) * 1000000000; + ns += after->tv_nsec - before->tv_nsec; + + return ns; +} + +static const char *suffix_name(const char *longname) +{ + size_t len = strlen(longname); + int i = len; + + /* longname name invalid */ + if (len < 1) { + SNDERR("invalid topology long name\n"); + return NULL; + } + + /* find the last '/' in the longname topology path */ + while (--i >= 0) { + if (longname[i] == '/') { + i += 1; /* skip / */ + return &longname[i]; + } + } + + /* no / in topology path, so use full path */ + return longname; +} + +/* + * IPC + * + * POSIX message queues are used for interprocess IPC messaging. + */ + +/* + * Initialise the IPC object. + */ +int plug_socket_path_init(struct plug_socket_desc *ipc, const char *tplg, const char *type, + int index) +{ + snprintf(ipc->path, NAME_SIZE, "/tmp/%s-%s", tplg, type); + return 0; +} + +/* + * Locking + * + * POSIX semaphores are used to block and synchronise audio between + * different threads and processes. + */ + +/* + * Initialise the lock object. + */ +int plug_lock_init(struct plug_sem_desc *lock, const char *tplg, const char *type, int index) +{ + const char *name = suffix_name(tplg); + + if (!name) + return -EINVAL; + + /* semaphores need the leading / */ + snprintf(lock->name, NAME_SIZE, "/lock-%s-%s-%d", name, type, index); + + return 0; +} + +/* + * SHM + * + * Shared memory is used for audio data and audio context sharing between + * threads and processes. + */ + +/* + * Initialise the SHM object. + */ +int plug_shm_init(struct plug_shm_desc *shm, const char *tplg, const char *type, int index) +{ + const char *name = suffix_name(tplg); + + if (!name) + return -EINVAL; + + snprintf(shm->name, NAME_SIZE, "/shm-%s-%s-%d", name, type, index); + shm->size = SHM_SIZE; + + return 0; +} + +/* + * Open an existing shared memory region using the SHM object. + */ +int plug_shm_open(struct plug_shm_desc *shm) +{ + struct stat status; + + /* open SHM to be used for low latency position */ + shm->fd = shm_open(shm->name, O_RDWR, + S_IRWXU | S_IRWXG); + if (shm->fd < 0) { + //SNDERR("failed to open SHM position %s: %s\n", + // shm->name, strerror(errno)); + return -errno; + } + + fstat(shm->fd, &status); + /* map it locally for context readback */ + shm->addr = mmap(NULL, status.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, shm->fd, 0); + if (!shm->addr) { + SNDERR("failed to mmap SHM position%s: %s\n", shm->name, strerror(errno)); + return -errno; + } + + return 0; +} + +static int plug_socket_timed_wait(struct plug_socket_desc *ipc, fd_set *fds, int timeout_ms, + bool write) +{ + struct timeval timeout; + int result; + + /* Set the timeout for select */ + timeout.tv_sec = 0; + timeout.tv_usec = timeout_ms * 1000; + + /* now wait for socket to be readable/writable */ + if (write) + result = select(ipc->socket_fd + 1, NULL, fds, NULL, &timeout); + else + result = select(ipc->socket_fd + 1, fds, NULL, NULL, &timeout); + + if (result == -1) { + SNDERR("error waiting for socket to be %s\n", write ? "writable" : "readable"); + return result; + } + + if (result == 0) { + SNDERR("IPC Socket %s timeout\n", write ? "write" : "read"); + return -ETIMEDOUT; + } + + /* socket ready for read/write */ + if (FD_ISSET(ipc->socket_fd, fds)) + return 0; + + /* socket not ready */ + return -EINVAL; +} + +static int plug_ipc_cmd_tx(struct plug_socket_desc *ipc, void *msg, size_t len) +{ + fd_set write_fds; + char mailbox[IPC3_MAX_MSG_SIZE]; + ssize_t bytes; + int err; + + if (len > IPC3_MAX_MSG_SIZE) { + SNDERR("ipc: message too big %d\n", len); + return -EINVAL; + } + memset(mailbox, 0, IPC3_MAX_MSG_SIZE); + memcpy(mailbox, msg, len); + + /* Wait for the socket to be writable */ + FD_ZERO(&write_fds); + FD_SET(ipc->socket_fd, &write_fds); + + err = plug_socket_timed_wait(ipc, &write_fds, 20, true); + if (err < 0) + return err; + + bytes = send(ipc->socket_fd, mailbox, IPC3_MAX_MSG_SIZE, 0); + if (bytes == -1) { + SNDERR("failed to send IPC message : %s\n", strerror(errno)); + return -errno; + } + + return bytes; +} + +static int plug_ipc_cmd_rx(struct plug_socket_desc *ipc, char mailbox[IPC3_MAX_MSG_SIZE]) +{ + fd_set read_fds; + int err; + + /* Wait for the socket to be readable */ + FD_ZERO(&read_fds); + FD_SET(ipc->socket_fd, &read_fds); + + err = plug_socket_timed_wait(ipc, &read_fds, 200, false); + if (err < 0) + return err; + + memset(mailbox, 0, IPC3_MAX_MSG_SIZE); + return recv(ipc->socket_fd, mailbox, IPC3_MAX_MSG_SIZE, 0); +} + +int plug_ipc_cmd_tx_rx(struct plug_socket_desc *ipc, void *msg, size_t len, void *reply, + size_t rlen) +{ + char mailbox[IPC3_MAX_MSG_SIZE]; + ssize_t bytes; + int err; + + /* send IPC message */ + bytes = plug_ipc_cmd_tx(ipc, msg, len); + if (bytes == -1) { + SNDERR("failed to send IPC message : %s\n", strerror(errno)); + return -errno; + } + + /* wait for response */ + memset(mailbox, 0, IPC3_MAX_MSG_SIZE); + bytes = plug_ipc_cmd_rx(ipc, mailbox); + if (bytes == -1) { + SNDERR("failed to read IPC message reply %s\n", strerror(errno)); + return -errno; + } + + /* no response or connection lost, try to restablish connection */ + if (bytes == 0) { + close(ipc->socket_fd); + err = plug_create_client_socket(ipc); + if (err < 0) { + SNDERR("failed to reestablish connection to SOF pipe IPC socket : %s", + strerror(err)); + return -errno; + } + + /* send IPC message again */ + bytes = plug_ipc_cmd_tx(ipc, msg, len); + if (bytes == -1) { + SNDERR("failed to send IPC message : %s\n", strerror(errno)); + return -errno; + } + + /* wait for response */ + memset(mailbox, 0, IPC3_MAX_MSG_SIZE); + bytes = plug_ipc_cmd_rx(ipc, mailbox); + if (bytes == -1) { + SNDERR("failed to read IPC message reply %s\n", strerror(errno)); + return -errno; + } + + /* connection lost again, quit now */ + if (bytes == 0) + return -errno; + } + + /* do the message work */ + if (rlen && reply) + memcpy(reply, mailbox, rlen); + + return 0; +} + +void plug_ctl_ipc_message(struct ipc4_module_large_config *config, int param_id, + size_t size, uint32_t module_id, uint32_t instance_id, + uint32_t type) +{ + config->primary.r.type = type; + config->primary.r.msg_tgt = SOF_IPC4_MESSAGE_TARGET_MODULE_MSG; + config->primary.r.rsp = SOF_IPC4_MESSAGE_DIR_MSG_REQUEST; + config->primary.r.module_id = module_id; + config->primary.r.instance_id = instance_id; + + config->extension.r.data_off_size = size; + config->extension.r.large_param_id = param_id; +} + +int plug_send_bytes_data(struct plug_socket_desc *ipc, uint32_t module_id, uint32_t instance_id, + struct sof_abi_hdr *abi) +{ + struct ipc4_module_large_config config = {{ 0 }}; + struct ipc4_message_reply reply; + void *msg; + int msg_size; + int err; + + /* configure the IPC message */ + plug_ctl_ipc_message(&config, abi->type, abi->size, module_id, instance_id, + SOF_IPC4_MOD_LARGE_CONFIG_SET); + + config.extension.r.final_block = 1; + config.extension.r.init_block = 1; + + /* allocate memory for IPC message */ + msg_size = sizeof(config) + abi->size; + msg = calloc(msg_size, 1); + if (!msg) + return -ENOMEM; + + /* set the IPC message data */ + memcpy(msg, &config, sizeof(config)); + memcpy(msg + sizeof(config), abi->data, abi->size); + + /* send the message and check status */ + err = plug_ipc_cmd_tx_rx(ipc, msg, msg_size, &reply, sizeof(reply)); + free(msg); + if (err < 0) { + SNDERR("failed to send IPC to set bytes data\n"); + return err; + } + + if (reply.primary.r.status != IPC4_SUCCESS) { + SNDERR("IPC failed with status %d\n", reply.primary.r.status); + return -EINVAL; + } + + return 0; +} + +int plug_socket_create(struct plug_socket_desc *ipc) +{ + struct sockaddr_un addr; + int sockfd; + + /* Check if the socket path already exists */ + if (access(ipc->path, F_OK) != -1) { + /* If it exists, remove it */ + if (unlink(ipc->path) == -1) { + SNDERR("unlink previous socket file"); + return -EINVAL; + } + } + + /* Create the socket */ + sockfd = socket(AF_UNIX, SOCK_STREAM, 0); + if (sockfd == -1) { + SNDERR("failed to create new socket"); + return sockfd; + } + + ipc->socket_fd = sockfd; + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, ipc->path, sizeof(addr.sun_path) - 1); + + /* Bind the socket to the address */ + if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) { + SNDERR("failed to bind new socket for IPC path\n"); + close(sockfd); + return -EINVAL; + } + + if (listen(sockfd, MAX_IPC_CLIENTS) == -1) { + SNDERR("failed to listen on socket for IPC\n"); + return -EINVAL; + } + + return 0; +} + +static int set_socket_nonblocking(int sockfd) +{ + int flags = fcntl(sockfd, F_GETFL, 0); + + if (flags == -1) { + SNDERR("fcntl(F_GETFL) failed"); + return -EINVAL; + } + + flags |= O_NONBLOCK; + if (fcntl(sockfd, F_SETFL, flags) == -1) { + SNDERR("fcntl(F_SETFL) failed"); + return -EINVAL; + } + + return 0; +} + +int plug_create_client_socket(struct plug_socket_desc *ipc) +{ + struct sockaddr_un addr; + int sockfd; + + sockfd = socket(AF_UNIX, SOCK_STREAM, 0); + if (sockfd == -1) { + SNDERR("error: failed to create sof-pipe IPC socket\n"); + return sockfd; + } + + if (set_socket_nonblocking(sockfd) < 0) { + close(sockfd); + return -EINVAL; + } + ipc->socket_fd = sockfd; + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, ipc->path, sizeof(addr.sun_path) - 1); + + /* Connect to the server (non-blocking) */ + if (connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) { + if (errno != EINPROGRESS) { + SNDERR("failed to connect to ipc socket"); + return -errno; + } + } + + return sockfd; +} diff --git a/tools/plugin/common.h b/tools/plugin/common.h new file mode 100644 index 000000000000..a64100c397cc --- /dev/null +++ b/tools/plugin/common.h @@ -0,0 +1,328 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2022-2023 Intel Corporation. All rights reserved. + * + * Author: Liam Girdwood + */ + +#ifndef __SOF_PLUGIN_COMMON_H__ +#define __SOF_PLUGIN_COMMON_H__ + +#include +#include +#include +#include +#include + +/* temporary - current MAXLEN is not define in UAPI header - fix pending */ +#ifndef SNDRV_CTL_ELEM_ID_NAME_MAXLEN +#define SNDRV_CTL_ELEM_ID_NAME_MAXLEN 44 +#endif + +#include + +#define IPC3_MAX_MSG_SIZE 384 +#define NAME_SIZE 256 + +#define MAX_CTLS 256 + +#define MS_TO_US(_msus) (_msus * 1000) +#define MS_TO_NS(_msns) (MS_TO_US(_msns * 1000)) + +#define MS_TO_US(_msus) (_msus * 1000) +#define MS_TO_NS(_msns) (MS_TO_US(_msns * 1000)) + +#define SEM_PERMS (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) + +#define SHM_SIZE (4096 * 64) /* get from topology - and set for context */ + +#define NUM_EP_CONFIGS 8 + +#define MAX_IPC_CLIENTS 5 + +/* + * Run with valgrind + * valgrind --trace-children=yes aplay -v -Dsof:blah.tplg,1,hw:1,2 -f dat /dev/zero + */ +//#define VALGRIND +#ifdef VALGRIND +#define DEBUG_TV_SECS 10 +#define DEBUG_RETRIES 1000 +#else +#define DEBUG_RETRIES 10 +#define DEBUG_TV_SECS 0 +#endif + +#define SOF_MAGIC "sofpipe" +#define MAX_VOLUME_SIZE 120 +#define MAX_DATA_SIZE 512 + +enum plugin_state { + SOF_PLUGIN_STATE_INIT = 0, + SOF_PLUGIN_STATE_READY = 1, + SOF_PLUGIN_STATE_DEAD = 2, + SOF_PLUGIN_STATE_STREAM_RUNNING = 3, + SOF_PLUGIN_STATE_STREAM_ERROR = 5, +}; + +struct plug_shm_ctl { + unsigned int module_id; + unsigned int instance_id; + unsigned int type; + unsigned int volume_table[MAX_VOLUME_SIZE]; + unsigned int index; + char data[MAX_DATA_SIZE]; + union { + struct snd_soc_tplg_mixer_control mixer_ctl; + struct snd_soc_tplg_enum_control enum_ctl; + struct snd_soc_tplg_bytes_control bytes_ctl; + }; +}; + +/* + * config.48k2c { + * rate 48000 + * channels 2 + * period_time 0 + * period_frames 6000 + * buffer_time 0 + * buffer_frames 24000 + * } + */ +struct plug_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; +}; + +/* + * :[pcm:card:dev:config[pcm:card:dev:config]...] + */ +struct plug_cmdline_item { + int pcm; + char card_name[44]; + char dev_name[44]; + char config_name[44]; +}; + +/* + * Endpoint pipeline configuration + */ +struct endpoint_hw_config { + int pipeline; + char card_name[44]; + char dev_name[44]; + char config_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 plug_shm_endpoint { + char magic[8]; /* SOF_MAGIC */ + uint64_t state; + uint32_t pipeline_id; + uint32_t comp_id; + uint32_t idx; + unsigned long rpos; /* current position in ring buffer */ + unsigned long rwrap; + unsigned long wpos; /* current position in ring buffer */ + unsigned long wwrap; + unsigned long buffer_size; /* buffer size */ + unsigned long wtotal; /* total frames copied */ + unsigned long rtotal; /* total frames copied */ + int frame_size; + char data[0]; // TODO: align this on SIMD/cache +}; + +struct plug_shm_glb_state { + char magic[8]; /* SOF_MAGIC */ + uint64_t size; /* size of this structure in bytes */ + uint64_t state; /* enum plugin_state */ + struct endpoint_hw_config ep_config[NUM_EP_CONFIGS]; + int num_ep_configs; + uint64_t num_ctls; /* number of ctls */ + struct plug_shm_ctl ctl[]; +}; + +struct plug_shm_desc { + /* SHM for stream context sync */ + int fd; + int size; + char name[NAME_SIZE]; + void *addr; +}; + +struct plug_socket_desc { + int socket_fd; + char path[NAME_SIZE]; +}; + +struct plug_sem_desc { + char name[NAME_SIZE]; + sem_t *sem; +}; + +struct plug_ctl_container { + struct snd_soc_tplg_ctl_hdr *tplg[MAX_CTLS]; + int updated[MAX_CTLS]; + int count; +}; + +static inline void *plug_ep_rptr(struct plug_shm_endpoint *ep) +{ + return ep->data + ep->rpos; +} + +static inline void *plug_ep_wptr(struct plug_shm_endpoint *ep) +{ + return ep->data + ep->wpos; +} + +static inline int plug_ep_wrap_rsize(struct plug_shm_endpoint *ep) +{ + return ep->buffer_size - ep->rpos; +} + +static inline int plug_ep_wrap_wsize(struct plug_shm_endpoint *ep) +{ + return ep->buffer_size - ep->wpos; +} + +static inline int plug_ep_get_free(struct plug_shm_endpoint *ep) +{ + if (ep->rwrap == ep->wwrap) { + /* calculate available bytes */ + if (ep->rpos < ep->wpos) + return ep->buffer_size - (ep->wpos - ep->rpos); + else + return ep->buffer_size; + } else { + return ep->rpos - ep->wpos; + } +} + +static inline int plug_ep_get_avail(struct plug_shm_endpoint *ep) +{ + if (ep->rwrap == ep->wwrap) { + /* calculate available bytes */ + if (ep->rpos < ep->wpos) + return ep->wpos - ep->rpos; + else + return 0; + } else { + return (ep->buffer_size - ep->rpos) + ep->wpos; + } +} + +static inline void *plug_ep_consume(struct plug_shm_endpoint *ep, unsigned int bytes) +{ + ep->rtotal += bytes; + ep->rpos += bytes; + + if (ep->rpos >= ep->buffer_size) { + ep->rpos -= ep->buffer_size; + ep->rwrap++; + } + + return ep->data + ep->rpos; +} + +static inline void *plug_ep_produce(struct plug_shm_endpoint *ep, unsigned int bytes) +{ + ep->wtotal += bytes; + ep->wpos += bytes; + + if (ep->wpos >= ep->buffer_size) { + ep->wpos -= ep->buffer_size; + ep->wwrap++; + } + + return ep->data + ep->wpos; +} + +/* + * SHM + */ +int plug_shm_init(struct plug_shm_desc *shm, const char *tplg, const char *type, int index); + +int plug_shm_create(struct plug_shm_desc *shm); + +int plug_shm_open(struct plug_shm_desc *shm); + +void plug_shm_free(struct plug_shm_desc *shm); + +/* + * IPC + */ +int plug_socket_path_init(struct plug_socket_desc *ipc, const char *tplg, const char *type, + int index); + +void plug_socket_free(struct plug_socket_desc *ipc); + +int plug_ipc_cmd_tx_rx(struct plug_socket_desc *ipc, void *msg, size_t len, void *reply, + size_t rlen); + +/* + * Locking + */ +int plug_lock_create(struct plug_sem_desc *lock); + +void plug_lock_free(struct plug_sem_desc *lock); + +int plug_lock_init(struct plug_sem_desc *lock, const char *tplg, const char *type, int index); + +int plug_lock_open(struct plug_sem_desc *lock); + +/* + * Timing. + */ +void plug_timespec_add_ms(struct timespec *ts, unsigned long ms); + +long plug_timespec_delta_ns(struct timespec *before, struct timespec *after); + +/* dump the IPC data - dont print lines of 0s */ +static inline void data_dump(void *vdata, size_t bytes) +{ + uint32_t *data = vdata; + size_t words = bytes >> 2; + int i; + + for (i = 0; i < words; i++) { + /* 4 words per line */ + if (i % 4 == 0) { + /* delete lines with all 0s */ + if (i > 0 && data[i - 3] == 0 && data[i - 2] == 0 && + data[i - 1] == 0 && data[i - 0] == 0) + printf("\r"); + else + printf("\n"); + + printf("0x%4.4x: 0x%8.8x", i, data[i]); + } else { + printf(" 0x%8.8x", data[i]); + } + } + printf("\n"); +} + +void plug_ctl_ipc_message(struct ipc4_module_large_config *config, int param_id, + size_t size, uint32_t module_id, uint32_t instance_id, + uint32_t type); +int plug_send_bytes_data(struct plug_socket_desc *ipc, uint32_t module_id, uint32_t instance_id, + struct sof_abi_hdr *abi); + +int plug_socket_create(struct plug_socket_desc *ipc); + +int plug_create_client_socket(struct plug_socket_desc *ipc); + +#endif diff --git a/tools/plugin/modules/CMakeLists.txt b/tools/plugin/modules/CMakeLists.txt new file mode 100644 index 000000000000..d737972cde7d --- /dev/null +++ b/tools/plugin/modules/CMakeLists.txt @@ -0,0 +1,59 @@ +# SPDX-License-Identifier: BSD-3-Clause + +# File SHM pipe module +add_library(sof_mod_shm MODULE + shm.c +) +sof_append_relative_path_definitions(sof_mod_shm) +target_include_directories(sof_mod_shm PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/.. + ${CMAKE_CURRENT_SOURCE_DIR}/../pipe + ${sof_source_directory}/src/audio) + +target_compile_options(sof_mod_shm PRIVATE -DPIC -g -O3 -Wall -Werror -DCONFIG_LIBRARY -imacros${config_h}) + +install(TARGETS sof_mod_shm + DESTINATION /usr/lib/x86_64-linux-gnu/alsa-lib) + +target_link_options(sof_mod_shm PRIVATE -Wl,--export-dynamic) + +target_include_directories(sof_mod_shm PRIVATE ${sof_install_directory}/include) +target_include_directories(sof_mod_shm PRIVATE ${parser_install_dir}/include) + +set_target_properties(sof_mod_shm + PROPERTIES + INSTALL_RPATH "${sof_install_directory}/alsa-lib" + INSTALL_RPATH_USE_LINK_PATH TRUE +) + + +# ALSA SOF pipe module +add_library(sof_mod_alsa MODULE + alsa.c +) +sof_append_relative_path_definitions(sof_mod_alsa) +target_include_directories(sof_mod_alsa PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/.. + ${CMAKE_CURRENT_SOURCE_DIR}/../pipe + ${sof_source_directory}/src/audio) + +target_compile_options(sof_mod_alsa PRIVATE -DPIC -g -O3 -Wmissing-prototypes + -Wimplicit-fallthrough -Wall -Werror -DCONFIG_LIBRARY -imacros${config_h}) + +install(TARGETS sof_mod_alsa + DESTINATION /usr/lib/x86_64-linux-gnu/alsa-lib) + +target_link_options(sof_mod_alsa PRIVATE -Wl,--export-dynamic) + +target_include_directories(sof_mod_alsa PRIVATE ${sof_install_directory}/include) +target_include_directories(sof_mod_alsa PRIVATE ${parser_install_dir}/include) + +set_target_properties(sof_mod_alsa + PROPERTIES + INSTALL_RPATH "${sof_install_directory}/alsa-lib" + INSTALL_RPATH_USE_LINK_PATH TRUE +) + +add_subdirectory(ov_noise_suppression) diff --git a/tools/plugin/modules/alsa.c b/tools/plugin/modules/alsa.c new file mode 100644 index 000000000000..69442c687136 --- /dev/null +++ b/tools/plugin/modules/alsa.c @@ -0,0 +1,788 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2022 Intel Corporation. All rights reserved. + +/* file component for reading/writing pcm samples to/from a file */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "pipe.h" + +SOF_DEFINE_REG_UUID(arecord); +DECLARE_TR_CTX(arecord_tr, SOF_UUID(arecord_uuid), LOG_LEVEL_INFO); + +SOF_DEFINE_REG_UUID(aplay); +DECLARE_TR_CTX(aplay_tr, SOF_UUID(aplay_uuid), LOG_LEVEL_INFO); + +static const struct comp_driver comp_arecord; +static const struct comp_driver comp_aplay; + +/* ALSA comp data */ +struct alsa_comp_data { + snd_pcm_t *handle; + snd_pcm_info_t *info; + snd_pcm_hw_params_t *hw_params; + snd_pcm_sw_params_t *sw_params; + snd_pcm_uframes_t period_frames; + snd_pcm_uframes_t buffer_frames; + char *pcm_name; + struct sof_ipc_stream_params params; + struct plug_shm_desc pcm; + struct plug_shm_endpoint *ctx; + struct plug_shm_desc glb; + struct plug_shm_glb_state *glb_ctx; + struct endpoint_hw_config *ep_hw; +#if CONFIG_IPC_MAJOR_4 + struct ipc4_base_module_cfg base_cfg; +#endif +}; + +static struct endpoint_hw_config *alsa_get_hw_config(struct comp_dev *dev) +{ + struct alsa_comp_data *cd = comp_get_drvdata(dev); + struct plug_shm_glb_state *glb = cd->glb_ctx; + + if (!glb->num_ep_configs) + return NULL; + + return glb->ep_config; +} + +static int alsa_alloc(struct comp_dev *dev) +{ + struct alsa_comp_data *cd = comp_get_drvdata(dev); + int err; + + /* get ALSA ready */ + err = snd_pcm_info_malloc(&cd->info); + if (err < 0) + goto error; + + err = snd_pcm_hw_params_malloc(&cd->hw_params); + if (err < 0) + goto error; + + err = snd_pcm_sw_params_malloc(&cd->sw_params); + if (err < 0) + goto error; + + comp_dbg(dev, "open done"); + return 0; + +error: + return err; +} + +static int alsa_close(struct comp_dev *dev) +{ + struct alsa_comp_data *cd = comp_get_drvdata(dev); + int ret = 0; + + comp_dbg(dev, "close"); + if (cd->handle) { + ret = snd_pcm_hw_free(cd->handle); + if (ret < 0) + comp_err(dev, "error: failed to snd_pcm_hw_free: %s\n", snd_strerror(ret)); + + ret = snd_pcm_close(cd->handle); + if (ret < 0) + comp_err(dev, "error: failed to snd_pcm_close: %s\n", snd_strerror(ret)); + + cd->handle = NULL; + } + + return ret; +} + +static void alsa_free(struct comp_dev *dev) +{ + struct alsa_comp_data *cd = comp_get_drvdata(dev); + + comp_dbg(dev, "alsa_free()"); + + snd_pcm_sw_params_free(cd->sw_params); + snd_pcm_hw_params_free(cd->hw_params); + snd_pcm_info_free(cd->info); + plug_shm_free(&cd->pcm); + free(cd); + free(dev); +} + +static struct comp_dev *alsa_new(const struct comp_driver *drv, + const struct comp_ipc_config *config, + const void *spec) +{ +#if CONFIG_IPC_MAJOR_4 + const struct ipc4_base_module_cfg *base_cfg = (struct ipc4_base_module_cfg *)spec; +#endif + struct comp_dev *dev; + struct alsa_comp_data *cd; + int err; + + dev = comp_alloc(drv, sizeof(*dev)); + if (!dev) + return NULL; + dev->ipc_config = *config; + + /* allocate memory for file comp data */ + cd = rzalloc(SOF_MEM_FLAG_USER, sizeof(*cd)); + if (!cd) + goto error; + + comp_set_drvdata(dev, cd); + memcpy(&cd->base_cfg, base_cfg, sizeof(struct ipc4_base_module_cfg)); + + /* use PCM ID to create shm */ + err = plug_shm_init(&cd->pcm, _sp->topology_name, "pcm", 1); + if (err < 0) { + comp_err(dev, "Error initializing pcm\n"); + goto error; + } + + // TODO: get the shm size for the buffer using a better method + //cd->pcm.size = 128 * 1024; + + /* mmap the SHM PCM */ + err = plug_shm_open(&cd->pcm); + if (err < 0) { + comp_err(dev, "Error open pcm shm"); + goto error; + } + cd->ctx = cd->pcm.addr; + + err = plug_shm_init(&cd->glb, _sp->topology_name, "ctx", 0); + if (err < 0) { + comp_err(dev, "Error initializing ctx\n"); + goto error; + } + + // TODO: get the shm size for the buffer using a better method + //cd->pcm.size = 128 * 1024; + + /* mmap the GLB ctx */ + err = plug_shm_open(&cd->glb); + if (err < 0) { + comp_err(dev, "Error opening glb ctx\n"); + goto error; + } + cd->glb_ctx = cd->glb.addr; + + /* alloc alsa context */ + err = alsa_alloc(dev); + if (err < 0) { + comp_err(dev, "Error allocating alsa context\n"); + goto error; + } + + return dev; + +error: + free(cd); + free(dev); + return NULL; +} + +static struct comp_dev *arecord_new(const struct comp_driver *drv, + const struct comp_ipc_config *config, const void *spec) +{ + struct comp_dev *dev; + struct alsa_comp_data *cd; + + comp_dbg(dev, "arecord_new()"); + + dev = alsa_new(drv, config, spec); + if (!dev) + return NULL; + + cd = comp_get_drvdata(dev); + cd->params.direction = SND_PCM_STREAM_CAPTURE; + + dev->state = COMP_STATE_READY; + return dev; +} + +static struct comp_dev *aplay_new(const struct comp_driver *drv, + const struct comp_ipc_config *config, + const void *spec) +{ + struct comp_dev *dev; + struct alsa_comp_data *cd; + + comp_dbg(dev, "aplay_new()"); + + dev = alsa_new(drv, config, spec); + if (!dev) + return NULL; + + cd = comp_get_drvdata(dev); + cd->params.direction = SND_PCM_STREAM_PLAYBACK; + + dev->state = COMP_STATE_READY; + return dev; +} + +static int set_params(struct comp_dev *dev) +{ + struct alsa_comp_data *cd = comp_get_drvdata(dev); + struct sof_ipc_stream_params *params = &cd->params; + int frame_fmt; + int err; + + err = snd_pcm_open(&cd->handle, cd->pcm_name, cd->params.direction, 0); + if (err < 0) { + comp_err(dev, "error: cant open PCM %s: %s\n", cd->pcm_name, snd_strerror(err)); + return err; + } + + err = snd_pcm_info(cd->handle, cd->info); + if (err < 0) { + comp_err(dev, "error: cant get PCM info: %s\n", snd_strerror(err)); + return err; + } + + /* set sample format */ + switch (params->frame_fmt) { + case SOF_IPC_FRAME_S16_LE: + frame_fmt = SND_PCM_FORMAT_S16_LE; + break; + case SOF_IPC_FRAME_S24_4LE: + frame_fmt = SND_PCM_FORMAT_S24_LE; + break; + case SOF_IPC_FRAME_S32_LE: + frame_fmt = SND_PCM_FORMAT_S32_LE; + break; + case SOF_IPC_FRAME_FLOAT: + frame_fmt = SND_PCM_FORMAT_FLOAT_LE; + break; + case SOF_IPC_FRAME_S24_3LE: + frame_fmt = SND_PCM_FORMAT_S24_3LE; + break; + default: + comp_err(dev, "error: invalid frame format %d for ALSA PCM\n", params->frame_fmt); + return -EINVAL; + } + + /* commit playback hw_params and sw_params. Set latency of 10ms to avoid glitches */ + if (cd->params.direction == SND_PCM_STREAM_PLAYBACK) + return snd_pcm_set_params(cd->handle, frame_fmt, SND_PCM_ACCESS_RW_INTERLEAVED, + params->channels, params->rate, 0, 10000); + + /* is sound card HW configuration valid ? */ + err = snd_pcm_hw_params_any(cd->handle, cd->hw_params); + if (err < 0) { + comp_err(dev, "error: cant get PCM hw_params: %s\n", snd_strerror(err)); + return err; + } + + /* set interleaved buffer format */ + err = snd_pcm_hw_params_set_access(cd->handle, cd->hw_params, + SND_PCM_ACCESS_RW_INTERLEAVED); + if (err < 0) { + comp_err(dev, "error: PCM can't set interleaved: %s\n", snd_strerror(err)); + return err; + } + + err = snd_pcm_hw_params_set_format(cd->handle, cd->hw_params, frame_fmt); + if (err < 0) { + comp_err(dev, "error: PCM can't set format %d: %s\n", + frame_fmt, snd_strerror(err)); + return err; + } + + /* set number of channels */ + err = snd_pcm_hw_params_set_channels(cd->handle, cd->hw_params, params->channels); + if (err < 0) { + comp_err(dev, "error: PCM can't set channels %d: %s\n", + params->channels, snd_strerror(err)); + return err; + } + + /* set sample rate */ + err = snd_pcm_hw_params_set_rate(cd->handle, cd->hw_params, params->rate, 0); + if (err < 0) { + comp_err(dev, "error: PCM can't set rate %d: %s\n", + params->rate, snd_strerror(err)); + return err; + } + + /* set period size TODO: get from topology */ + err = snd_pcm_hw_params_set_period_size(cd->handle, cd->hw_params, + cd->period_frames, 0); + if (err < 0) { + comp_err(dev, "error: PCM can't set period size %ld frames: %s\n", + cd->period_frames, snd_strerror(err)); + return err; + } + + /* set buffer size: TODO: get from topology */ + err = snd_pcm_hw_params_set_buffer_size_near(cd->handle, cd->hw_params, + &cd->buffer_frames); + if (err < 0) { + comp_err(dev, "error: PCM can't set buffer size %ld frames: %s\n", + cd->buffer_frames, snd_strerror(err)); + return err; + } + + /* commit the hw params */ + err = snd_pcm_hw_params(cd->handle, cd->hw_params); + if (err < 0) { + comp_err(dev, "error: PCM can't commit hw_params: %s\n", snd_strerror(err)); + snd_pcm_hw_params_dump(cd->hw_params, SND_OUTPUT_STDIO); + return err; + } + + /* get the initial SW params */ + err = snd_pcm_sw_params_current(cd->handle, cd->sw_params); + if (err < 0) { + comp_err(dev, "error: PCM can't get sw params: %s\n", snd_strerror(err)); + return err; + } + + /* set the avail min to the period size */ + err = snd_pcm_sw_params_set_avail_min(cd->handle, cd->sw_params, cd->period_frames); + if (err < 0) { + comp_err(dev, "error: PCM can't set avail min: %s\n", snd_strerror(err)); + return err; + } + + /* PCM should start after receiving first periods worth of data */ + err = snd_pcm_sw_params_set_start_threshold(cd->handle, cd->sw_params, 1); + if (err < 0) { + comp_err(dev, "error: PCM can't set start threshold: %s\n", snd_strerror(err)); + return err; + } + + /* PCM should stop if only buffer_frame worth of data is available */ + err = snd_pcm_sw_params_set_stop_threshold(cd->handle, cd->sw_params, + cd->buffer_frames); + if (err < 0) { + comp_err(dev, "error: PCM can't set stop threshold: %s\n", snd_strerror(err)); + return err; + } + + /* commit the sw params */ + if (snd_pcm_sw_params(cd->handle, cd->sw_params) < 0) { + comp_err(dev, "error: PCM can't commit sw_params: %s\n", snd_strerror(err)); + snd_pcm_sw_params_dump(cd->sw_params, SND_OUTPUT_STDIO); + return err; + } + + comp_dbg(dev, "params set"); + return 0; +} + +static int alsa_dai_get_hw_params(struct comp_dev *dev, + struct sof_ipc_stream_params *params, int dir); + +/** + * \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 arecord_params(struct comp_dev *dev, struct sof_ipc_stream_params *params) +{ + struct comp_buffer *buffer; + struct alsa_comp_data *cd = comp_get_drvdata(dev); + int ret; + + comp_dbg(dev, "arecord params"); + + ret = alsa_dai_get_hw_params(dev, params, cd->params.direction); + + if (params->direction != SND_PCM_STREAM_CAPTURE) { + comp_err(dev, "alsa_params(): pcm params invalid direction."); + return -EINVAL; + } + + /* params can be aligned to match pipeline here */ + ret = comp_verify_params(dev, 0, params); + if (ret < 0) { + comp_err(dev, "alsa_params(): pcm params verification failed."); + return ret; + } + memcpy(&cd->params, params, sizeof(*params)); + + /* file component sink/source buffer period count */ + buffer = comp_dev_get_first_data_consumer(dev); + audio_buffer_reset(&buffer->audio_buffer); + + comp_dbg(dev, "prepare done ret = %d", ret); + + return 0; +} + +static int aplay_params(struct comp_dev *dev, struct sof_ipc_stream_params *params) +{ + struct comp_buffer *buffer; + struct alsa_comp_data *cd = comp_get_drvdata(dev); + int ret; + + comp_dbg(dev, "aplay params"); + + ret = alsa_dai_get_hw_params(dev, params, cd->params.direction); + + if (params->direction != SND_PCM_STREAM_PLAYBACK) { + comp_err(dev, "alsa_params(): pcm params invalid direction."); + return -EINVAL; + } + + /* params can be aligned to match pipeline here */ + ret = comp_verify_params(dev, 0, params); + if (ret < 0) { + comp_err(dev, "alsa_params(): pcm params verification failed."); + return ret; + } + memcpy(&cd->params, params, sizeof(*params)); + + /* file component sink/source buffer period count */ + buffer = comp_dev_get_first_data_producer(dev); + audio_buffer_reset(&buffer->audio_buffer); + + comp_dbg(dev, "prepare done ret = %d", ret); + return 0; +} + +static int alsa_trigger(struct comp_dev *dev, int cmd) +{ + int err; + + /* trigger is handled automatically by ALSA start threshold */ + comp_dbg(dev, "trigger cmd %d", cmd); + + switch (cmd) { + case COMP_TRIGGER_PAUSE: + case COMP_TRIGGER_STOP: + err = alsa_close(dev); + if (err < 0) { + comp_err(dev, "error: cant stop pipeline"); + return err; + } + break; + case COMP_TRIGGER_RELEASE: + case COMP_TRIGGER_START: + err = set_params(dev); + if (err < 0) { + comp_err(dev, "error: cant stop pipeline"); + return err; + } + break; + default: + break; + } + + return comp_set_state(dev, cmd); +} + +/* used to pass standard and bespoke commands (with data) to component */ +static int alsa_cmd(struct comp_dev *dev, int cmd, void *data, + int max_data_size) +{ + return 0; +} + +/* + * copy and process stream samples + * returns the number of bytes copied + */ +static int arecord_copy(struct comp_dev *dev) +{ + struct alsa_comp_data *cd = comp_get_drvdata(dev); + struct comp_buffer *buffer; + struct audio_stream *sink; + snd_pcm_sframes_t frames; + snd_pcm_uframes_t free; + snd_pcm_uframes_t total = 0; + unsigned int frame_bytes; + void *pos; + + switch (dev->state) { + case COMP_STATE_ACTIVE: + break; + default: + return -EINVAL; + } + + /* file component sink buffer */ + buffer = comp_dev_get_first_data_consumer(dev); + sink = &buffer->stream; + pos = sink->w_ptr; + + //FIX: this will fill buffer and higher latency, use period size + free = MIN(audio_stream_get_free_frames(sink), cd->period_frames); + frame_bytes = audio_stream_frame_bytes(sink); + + while (free) { + frames = MIN(free, audio_stream_frames_without_wrap(sink, pos)); + + /* read PCM samples from file */ + frames = snd_pcm_readi(cd->handle, pos, frames); + if (frames < 0) { + if (frames == -EPIPE) { + snd_pcm_prepare(cd->handle); + continue; + } + comp_err(dev, "failed to read: %s: %s\n", + cd->pcm_name, snd_strerror(frames)); + return frames; + } + + free -= frames; + pos = audio_stream_wrap(sink, pos + frames * frame_bytes); + total += frames; + } + + /* update sink buffer pointers */ + comp_update_buffer_produce(buffer, total * frame_bytes); + comp_dbg(dev, "read %d frames", total); + + return 0; +} + +/* + * copy and process stream samples + * returns the number of bytes copied + */ +static int aplay_copy(struct comp_dev *dev) +{ + struct alsa_comp_data *cd = comp_get_drvdata(dev); + struct comp_buffer *buffer; + struct audio_stream *source; + snd_pcm_sframes_t frames; + snd_pcm_sframes_t avail; + snd_pcm_uframes_t total = 0; + unsigned int frame_bytes; + void *pos; + + switch (dev->state) { + case COMP_STATE_ACTIVE: + break; + default: + return -EINVAL; + } + + /* file component source buffer */ + buffer = comp_dev_get_first_data_producer(dev); + source = &buffer->stream; + pos = source->r_ptr; + avail = audio_stream_get_avail_frames(source); + frame_bytes = audio_stream_frame_bytes(source); + + while (avail > 0) { + frames = MIN(avail, audio_stream_frames_without_wrap(source, pos)); + + /* write PCM samples to PCM */ + frames = snd_pcm_writei(cd->handle, pos, frames); + if (frames < 0) { + if (frames == -EPIPE) { + snd_pcm_prepare(cd->handle); + continue; + } + comp_err(dev, "failed to write: %s: %s\n", + cd->pcm_name, snd_strerror(frames)); + return frames; + } + avail -= frames; + pos = audio_stream_wrap(source, pos + frames * frame_bytes); + total += frames; + } + + /* update sink buffer pointers */ + comp_update_buffer_consume(buffer, total * frame_bytes); + comp_dbg(dev, "wrote %d bytes", total * frame_bytes); + + return 0; +} + +static int alsa_prepare(struct comp_dev *dev) +{ + int ret = 0; + + comp_dbg(dev, "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; + + return ret; +} + +static int alsa_reset(struct comp_dev *dev) +{ + comp_dbg(dev, "reset"); + + comp_set_state(dev, COMP_TRIGGER_RESET); + + return 0; +} + +/* + * TODO: we pass the DAI topology config back up the pipeline so + * that upstream/downstream can be configured. Needs to be configured + * at stream runtime instead of at topology load time. + */ +static int alsa_dai_get_hw_params(struct comp_dev *dev, struct sof_ipc_stream_params *params, + int dir) +{ + struct alsa_comp_data *cd = comp_get_drvdata(dev); + struct endpoint_hw_config *ep_hw; + char pcm_name[128]; + + comp_dbg(dev, "get_hw_params"); + + /* get our hw config from cmdline and conf file */ + ep_hw = alsa_get_hw_config(dev); + if (!ep_hw) { + comp_err(dev, "error: failed to get hw config %d\n"); + return -EINVAL; + } + cd->ep_hw = ep_hw; + + /* PCM name comes from cmd line - "default" dev means dont use dev */ + if (!strncmp(cd->ep_hw->dev_name, "default", sizeof(cd->ep_hw->dev_name))) { + snprintf(pcm_name, sizeof(pcm_name), "%s", cd->ep_hw->card_name); + } else { + snprintf(pcm_name, sizeof(pcm_name), "hw:%s,%s", + cd->ep_hw->card_name, cd->ep_hw->dev_name); + } + cd->pcm_name = strdup(pcm_name); + comp_dbg(dev, "using ALSA card %s", cd->pcm_name); + + /* set default config - get from cmdline and plugin config */ + cd->params.rate = cd->ep_hw->rate; + cd->params.channels = cd->ep_hw->channels; + cd->buffer_frames = cd->ep_hw->buffer_frames; + cd->period_frames = cd->ep_hw->period_frames; + + /* ALSA API uses frames, SOF buffer uses bytes */ + switch (cd->ep_hw->format) { + case SND_PCM_FORMAT_S16_LE: + cd->params.frame_fmt = SOF_IPC_FRAME_S16_LE; + cd->params.buffer.size = cd->ep_hw->buffer_frames * 2; + break; + case SND_PCM_FORMAT_S24_LE: + cd->params.frame_fmt = SOF_IPC_FRAME_S24_4LE; + cd->params.buffer.size = cd->ep_hw->buffer_frames * 4; + break; + case SND_PCM_FORMAT_S32_LE: + cd->params.frame_fmt = SOF_IPC_FRAME_S32_LE; + cd->params.buffer.size = cd->ep_hw->buffer_frames * 4; + break; + case SND_PCM_FORMAT_FLOAT: + cd->params.frame_fmt = SOF_IPC_FRAME_FLOAT; + cd->params.buffer.size = cd->ep_hw->buffer_frames * 4; + break; + case SND_PCM_FORMAT_S24_3LE: + cd->params.frame_fmt = SOF_IPC_FRAME_S24_3LE; + cd->params.buffer.size = cd->ep_hw->buffer_frames * 3; + break; + default: + comp_err(dev, "error: invalid frame format %d for ALSA PCM\n", params->frame_fmt); + return -EINVAL; + } + + memcpy(params, &cd->params, sizeof(*params)); + + comp_dbg(dev, "rate %d", params->rate); + comp_dbg(dev, "frame format %d", params->frame_fmt); + comp_dbg(dev, "channels %d", params->channels); + comp_dbg(dev, "buffer frames %d", cd->buffer_frames); + comp_dbg(dev, "period frames %d", cd->period_frames); + comp_dbg(dev, "direction %d", params->direction); + + return 0; +} + +static int alsa_get_attribute(struct comp_dev *dev, uint32_t type, void *value) +{ + struct alsa_comp_data *cd = comp_get_drvdata(dev); + + switch (type) { + case COMP_ATTR_BASE_CONFIG: + memcpy_s(value, sizeof(struct ipc4_base_module_cfg), + &cd->base_cfg, sizeof(struct ipc4_base_module_cfg)); + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct comp_driver comp_arecord = { + .type = SOF_COMP_FILEREAD, + .uid = SOF_RT_UUID(arecord_uuid), + .tctx = &arecord_tr, + .ops = { + .create = arecord_new, + .free = alsa_free, + .params = arecord_params, + .cmd = alsa_cmd, + .trigger = alsa_trigger, + .copy = arecord_copy, + .prepare = alsa_prepare, + .reset = alsa_reset, + .dai_get_hw_params = alsa_dai_get_hw_params, + .get_attribute = alsa_get_attribute, + }, +}; + +static const struct comp_driver comp_aplay = { + .type = SOF_COMP_FILEWRITE, + .uid = SOF_RT_UUID(aplay_uuid), + .tctx = &aplay_tr, + .ops = { + .create = aplay_new, + .free = alsa_free, + .params = aplay_params, + .cmd = alsa_cmd, + .trigger = alsa_trigger, + .copy = aplay_copy, + .prepare = alsa_prepare, + .reset = alsa_reset, + .dai_get_hw_params = alsa_dai_get_hw_params, + .get_attribute = alsa_get_attribute, + }, +}; + +static struct comp_driver_info comp_arecord_info = { + .drv = &comp_arecord, +}; + +static struct comp_driver_info comp_aplay_info = { + .drv = &comp_aplay, +}; + +static void sys_comp_alsa_init(void) +{ + comp_register(&comp_arecord_info); + comp_register(&comp_aplay_info); +} + +DECLARE_MODULE(sys_comp_alsa_init); diff --git a/tools/plugin/modules/ov_noise_suppression/CMakeLists.txt b/tools/plugin/modules/ov_noise_suppression/CMakeLists.txt new file mode 100644 index 000000000000..039e9056ee45 --- /dev/null +++ b/tools/plugin/modules/ov_noise_suppression/CMakeLists.txt @@ -0,0 +1,42 @@ +# SPDX-License-Identifier: BSD-3-Clause + +# Noise suppression module +find_package(OpenVINO COMPONENTS Runtime) + +if(OpenVINO_FOUND) +add_library(sof_ns_interface STATIC noise_suppression_interface.cpp) +target_link_libraries(sof_ns_interface PRIVATE -Wl,--export-dynamic) +target_link_libraries(sof_ns_interface PRIVATE openvino::runtime) +target_compile_options(sof_ns_interface PRIVATE -fPIC) +target_include_directories(sof_ns_interface PRIVATE ${sof_source_directory}/src/include) +target_include_directories(sof_ns_interface PRIVATE ${sof_source_directory}/posix/include) +target_include_directories(sof_ns_interface PRIVATE ${sof_source_directory}/src/arch/host/include) +target_include_directories(sof_ns_interface PRIVATE ${sof_source_directory}/src/platform/posix/include) +set_target_properties(sof_ns_interface PROPERTIES LINKER_LANGUAGE CXX) +sof_append_relative_path_definitions(sof_ns_interface) +install(TARGETS sof_ns_interface DESTINATION /usr/lib/x86_64-linux-gnu/alsa-lib) + +add_library(sof_ns MODULE noise_suppression.c) +target_link_libraries(sof_ns PRIVATE sof_ns_interface) +target_link_libraries(sof_ns PRIVATE -Wl,--export-dynamic) +sof_append_relative_path_definitions(sof_ns) +target_compile_options(sof_ns PRIVATE -DPIC -g -O3 -Wall -Werror -DCONFIG_LIBRARY -imacros${config_h}) +install(TARGETS sof_ns DESTINATION /usr/lib/x86_64-linux-gnu/alsa-lib) + +target_include_directories(sof_ns PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/.. + ${CMAKE_CURRENT_SOURCE_DIR}/../pipe + ${sof_source_directory}/src/audio) + + +target_link_options(sof_ns PRIVATE -Wl,--export-dynamic) +target_include_directories(sof_ns PRIVATE ${sof_install_directory}/include) +target_include_directories(sof_ns PRIVATE ${parser_install_dir}/include) + +set_target_properties(sof_ns + PROPERTIES + INSTALL_RPATH "${sof_install_directory}/alsa-lib" + INSTALL_RPATH_USE_LINK_PATH TRUE +) +endif() diff --git a/tools/plugin/modules/ov_noise_suppression/noise_suppression.c b/tools/plugin/modules/ov_noise_suppression/noise_suppression.c new file mode 100644 index 000000000000..84ed62ab394c --- /dev/null +++ b/tools/plugin/modules/ov_noise_suppression/noise_suppression.c @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2024 Intel Corporation. All rights reserved. +// +// Author: Ranjani Sridharan + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "noise_suppression_interface.h" + +LOG_MODULE_REGISTER(ns, CONFIG_SOF_LOG_LEVEL); + +SOF_DEFINE_REG_UUID(ns); + +DECLARE_TR_CTX(ns_comp_tr, SOF_UUID(ns_uuid), LOG_LEVEL_INFO); + +static int ns_free(struct processing_module *mod) +{ + ns_handle handle = module_get_private_data(mod); + + ov_ns_free(handle); + return 0; +} + +static int ns_init(struct processing_module *mod) +{ + struct module_data *mod_data = &mod->priv; + + return ov_ns_init(&mod_data->private); +} + +static int +ns_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) +{ + ns_handle handle = module_get_private_data(mod); + int ret; + + ret = ov_ns_process(handle, input_buffers, num_input_buffers, + output_buffers, num_output_buffers); + if (ret < 0) + return ret; + + module_update_buffer_position(&input_buffers[0], &output_buffers[0], ret); + + return 0; +} + +static const struct module_interface ns_interface = { + .init = ns_init, + .process_audio_stream = ns_process, + .free = ns_free +}; + +DECLARE_MODULE_ADAPTER(ns_interface, ns_uuid, ns_comp_tr); +SOF_MODULE_INIT(ns, sys_comp_module_ns_interface_init); diff --git a/tools/plugin/modules/ov_noise_suppression/noise_suppression_interface.cpp b/tools/plugin/modules/ov_noise_suppression/noise_suppression_interface.cpp new file mode 100644 index 000000000000..fa74736d062f --- /dev/null +++ b/tools/plugin/modules/ov_noise_suppression/noise_suppression_interface.cpp @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2024 Intel Corporation. All rights reserved. +// +// Author: Ranjani Sridharan + + +#include +#include +#include +#include + +#include "noise_suppression_interface.h" +#define NS_MAX_SOURCE_CHANNELS 2 + +extern "C" { + struct ns_data { + std::shared_ptr model; + std::vector> state_names; + ov::InferRequest infer_request[NS_MAX_SOURCE_CHANNELS]; + ov::Shape inp_shape; + int iter; + }; + + int ov_ns_init(ns_handle *handle) { + struct ns_data *nd; + ov::OutputVector inputs, outputs; + ov::Core core; + size_t state_size = 0; + const char* model_name = std::getenv("NOISE_SUPPRESSION_MODEL_NAME"); + std::string device("CPU"); + int i; + + nd = new ns_data(); + if(!nd) + return -ENOMEM; + + *handle = nd; + nd->model = core.read_model(model_name); + inputs = nd->model->inputs(); + outputs = nd->model->outputs(); + + /* get state name pairs */ + for (size_t i = 0; i < inputs.size(); i++) { + std::string inp_state_name = inputs[i].get_any_name(); + if (inp_state_name.find("inp_state_") == std::string::npos) + continue; + + std::string out_state_name(inp_state_name); + out_state_name.replace(0, 3, "out"); + + /* find corresponding output state */ + if (outputs.end() == std::find_if(outputs.begin(), outputs.end(), [&out_state_name](const ov::Output& output) { + return output.get_any_name() == out_state_name; + })) + return -EINVAL; + + nd->state_names.emplace_back(inp_state_name, out_state_name); + + ov::Shape shape = inputs[i].get_shape(); + size_t tensor_size = std::accumulate(shape.begin(), shape.end(), 1, + std::multiplies()); + + state_size += tensor_size; + } + + if (!state_size) + return -EINVAL; + + /* + * query the list of available devices and use NPU if available, otherwise use + * CPU by default + */ + std::vector available_devices = core.get_available_devices(); + for (auto &s: available_devices) { + if (!s.compare("NPU")) { + device.assign("NPU"); + break; + } + } + + /* save the infer_request objects for each channel separately */ + ov::CompiledModel compiled_model = core.compile_model(nd->model, device, {}); + for (i = 0; i < NS_MAX_SOURCE_CHANNELS; i++) + nd->infer_request[i] = compiled_model.create_infer_request(); + + nd->inp_shape = nd->model->input("input").get_shape(); + + return 0; + } + + void ov_ns_free(ns_handle handle) { + struct ns_data *nd = (struct ns_data *)handle; + + delete nd; + } + + int ov_ns_process(ns_handle handle, + struct input_stream_buffer *input_buffers, int num_input_buffers, + struct output_stream_buffer *output_buffers, int num_output_buffers) + { + struct audio_stream *source = (struct audio_stream *)input_buffers[0].data; + struct audio_stream *sink = (struct audio_stream *)output_buffers[0].data; + struct ns_data *nd = (struct ns_data *)handle; + std::vector inp_wave_fp32, out_wave_fp32; + /* only 16-bit supported for now */ + int16_t *input_data = (int16_t *)audio_stream_get_rptr(source); + int16_t *output_data = (int16_t *)audio_stream_get_wptr(sink); + uint32_t frame_count = input_buffers[0].size; + float scale = 1.0f / std::numeric_limits::max(); + int i, j, ch; + + /* + * The noise suppression model only supports mono, so process each channel + * separately + */ + inp_wave_fp32.resize(frame_count, 0); + out_wave_fp32.resize(frame_count, 0); + + for (ch = 0; ch < NS_MAX_SOURCE_CHANNELS; ch++) { + /* split each channel samples and convert to floating point */ + for (i = ch, j = 0; j < frame_count; i+=2,j++) { + void *inp = &input_data[i]; + + /* wrap if needed */ + if (inp >= source->end_addr) + inp = (char *)source->addr + + ((char *)inp - (char *)source->end_addr); + + inp_wave_fp32[j] = (float)(*(int16_t *)inp) * scale; + } + + ov::Tensor input_tensor(ov::element::f32, nd->inp_shape, + &inp_wave_fp32[0]); + nd->infer_request[ch].set_tensor("input", input_tensor); + + for (auto& state_name: nd->state_names) { + const std::string& inp_state_name = state_name.first; + const std::string& out_state_name = state_name.second; + ov::Tensor state_tensor; + ov::Shape state_shape; + + state_shape = nd->model->input(inp_state_name).get_shape(); + if (nd->iter > 0) { + /* + * set input state by corresponding output state from prev + * infer + */ + state_tensor = + nd->infer_request[ch].get_tensor(out_state_name); + } else { + state_tensor = ov::Tensor(ov::element::f32, state_shape); + std::memset(state_tensor.data(), 0, + state_tensor.get_byte_size()); + } + nd->infer_request[ch].set_tensor(inp_state_name, state_tensor); + } + nd->infer_request[ch].infer(); + + /* process output */ + float* src = nd->infer_request[ch].get_tensor("output").data(); + float* dst = &out_wave_fp32[0]; + std::memcpy(dst, src, frame_count * sizeof(float)); + + /* convert back to int and write back to output buffer */ + for (i = 0, j = ch; i < frame_count; i++,j+=2) { + float v = out_wave_fp32[i]; + void *out = &output_data[j]; + + /* wrap if needed */ + if (out >= sink->end_addr) + out = (char *)sink->addr + + ((char *)out - (char *)sink->end_addr); + + v = std::clamp(v, -1.0f, +1.0f); + *(int16_t *)out = (int16_t)(v * std::numeric_limits::max()); + } + } + + nd->iter++; + + /* return frame_count so the input and output buffers can be updated accordingly */ + return frame_count; + } +} /* extern "C" */ + diff --git a/tools/plugin/modules/ov_noise_suppression/noise_suppression_interface.h b/tools/plugin/modules/ov_noise_suppression/noise_suppression_interface.h new file mode 100644 index 000000000000..5005e1f58161 --- /dev/null +++ b/tools/plugin/modules/ov_noise_suppression/noise_suppression_interface.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2024 Intel Corporation. All rights reserved. + * + * Author: Ranjani Sridharan + * + */ + +#ifndef _NOISE_SUPPRESSION_INTERFACE_H +#define _NOISE_SUPPRESSION_INTERFACE_H + +#ifdef __cplusplus +extern "C" { +#endif + typedef void *ns_handle; + int ov_ns_init(ns_handle *handle); + void ov_ns_free(ns_handle handle); + int ov_ns_process(ns_handle handle, + struct input_stream_buffer *input_buffers, int num_input_buffers, + struct output_stream_buffer *output_buffers, int num_output_buffers); + +#ifdef __cplusplus +} +#endif + +#endif /*_NOISE_SUPPRESSION_INTERFACE_H */ diff --git a/tools/plugin/modules/shm.c b/tools/plugin/modules/shm.c new file mode 100644 index 000000000000..0ddf297adcd1 --- /dev/null +++ b/tools/plugin/modules/shm.c @@ -0,0 +1,445 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2022 Intel Corporation. All rights reserved. + +/* shm component for reading/writing pcm samples to/from a shm */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pipe.h" + +SOF_DEFINE_REG_UUID(shmread); +DECLARE_TR_CTX(shmread_tr, SOF_UUID(shmread_uuid), LOG_LEVEL_INFO); + +SOF_DEFINE_REG_UUID(shmwrite); +DECLARE_TR_CTX(shmwrite_tr, SOF_UUID(shmwrite_uuid), LOG_LEVEL_INFO); + +static const struct comp_driver comp_shmread; +static const struct comp_driver comp_shmwrite; + +/* shm comp data */ +struct shm_comp_data { + /* PCM data */ + struct plug_shm_desc pcm; + struct plug_shm_endpoint *ctx; +#if CONFIG_IPC_MAJOR_4 + struct ipc4_base_module_cfg base_cfg; +#endif +}; + +static int shm_process_new(struct comp_dev *dev, + const struct comp_ipc_config *config, + const void *spec) +{ + struct shm_comp_data *cd = comp_get_drvdata(dev); + struct plug_shm_endpoint *ctx; + int ret; + + comp_dbg(dev, "shm new()"); + + /* FIXME: use PCM ID to create shm */ + ret = plug_shm_init(&cd->pcm, _sp->topology_name, "pcm", 1); + if (ret < 0) + return ret; + + // TODO: get the shm size for the buffer using a better method + cd->pcm.size = 128 * 1024; + + /* mmap the SHM PCM */ + ret = plug_shm_create(&cd->pcm); + if (ret < 0) + return ret; + + cd->ctx = cd->pcm.addr; + ctx = cd->ctx; + ctx->buffer_size = cd->pcm.size; + memset(ctx, 0, sizeof(*ctx)); + ctx->comp_id = config->id; + ctx->pipeline_id = config->pipeline_id; + ctx->state = SOF_PLUGIN_STATE_INIT; + dev->state = COMP_STATE_READY; + + return 0; +} + +static void shm_free(struct comp_dev *dev) +{ + struct shm_comp_data *cd = comp_get_drvdata(dev); + + cd->ctx = NULL; + + plug_shm_free(&cd->pcm); + shm_unlink(cd->pcm.name); + + free(cd); + free(dev); +} + +static struct comp_dev *shm_new(const struct comp_driver *drv, + const struct comp_ipc_config *config, + const void *spec, int direction) +{ + struct comp_dev *dev; +#if CONFIG_IPC_MAJOR_4 + const struct ipc4_base_module_cfg *base_cfg = (struct ipc4_base_module_cfg *)spec; +#endif + struct shm_comp_data *cd; + int err; + + dev = comp_alloc(drv, sizeof(*dev)); + if (!dev) + return NULL; + dev->ipc_config = *config; + + /* allocate memory for shm comp data */ + cd = rzalloc(SOF_MEM_FLAG_USER, sizeof(*cd)); + if (!cd) + goto error; + + comp_set_drvdata(dev, cd); + memcpy(&cd->base_cfg, base_cfg, sizeof(struct ipc4_base_module_cfg)); + + dev->direction = direction; + err = shm_process_new(dev, config, spec); + if (err < 0) { + free(cd); + free(dev); + return NULL; + } + dev->direction_set = true; + + dev->state = COMP_STATE_READY; + return dev; + +error: + free(dev); + return NULL; +} + +static struct comp_dev *shmwrite_new(const struct comp_driver *drv, + const struct comp_ipc_config *config, + const void *spec) +{ + return shm_new(drv, config, spec, SOF_IPC_STREAM_PLAYBACK); +} + +static struct comp_dev *shmread_new(const struct comp_driver *drv, + const struct comp_ipc_config *config, + const void *spec) +{ + return shm_new(drv, config, spec, SOF_IPC_STREAM_CAPTURE); +} + +/** + * \brief Sets shm 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 shmread_params(struct comp_dev *dev, struct sof_ipc_stream_params *params) +{ + struct shm_comp_data *cd = comp_get_drvdata(dev); + struct plug_shm_endpoint *ctx = cd->ctx; + int ret; + +#if CONFIG_IPC_MAJOR_4 + ipc4_base_module_cfg_to_stream_params(&cd->base_cfg, params); +#endif + + comp_err(dev, "forme_fmt %d channels %d", params->frame_fmt, params->channels); + + ret = comp_verify_params(dev, 0, params); + if (ret < 0) { + comp_err(dev, "shm_params(): pcm params verification failed."); + return ret; + } + ctx->state = SOF_PLUGIN_STATE_READY; + + return 0; +} + +static int shmwrite_params(struct comp_dev *dev, struct sof_ipc_stream_params *params) +{ + struct shm_comp_data *cd = comp_get_drvdata(dev); + struct plug_shm_endpoint *ctx = cd->ctx; + int ret; + + ret = comp_verify_params(dev, 0, params); + if (ret < 0) { + comp_err(dev, "shm_params(): pcm params verification failed."); + return ret; + } + ctx->state = SOF_PLUGIN_STATE_READY; + + return 0; +} + +static int shm_trigger(struct comp_dev *dev, int cmd) +{ + struct shm_comp_data *cd = comp_get_drvdata(dev); + struct plug_shm_endpoint *ctx = cd->ctx; + + comp_dbg(dev, "shm_trigger(%d)", cmd); + + switch (cmd) { + case COMP_TRIGGER_START: + case COMP_TRIGGER_RELEASE: + ctx->state = SOF_PLUGIN_STATE_STREAM_RUNNING; + break; + case COMP_TRIGGER_STOP: + case COMP_TRIGGER_PAUSE: + ctx->state = SOF_PLUGIN_STATE_READY; + break; + case COMP_TRIGGER_RESET: + ctx->state = SOF_PLUGIN_STATE_INIT; + break; + case COMP_TRIGGER_XRUN: + ctx->state = SOF_PLUGIN_STATE_STREAM_ERROR; + break; + default: + break; + } + + return comp_set_state(dev, cmd); +} + +static int shm_cmd(struct comp_dev *dev, int cmd, void *data, int max_data_size) +{ + return 0; +} + +/* + * copy from local SOF buffer to remote SHM buffer + */ +static int shmread_copy(struct comp_dev *dev) +{ + struct shm_comp_data *cd = comp_get_drvdata(dev); + struct plug_shm_endpoint *ctx = cd->ctx; + struct comp_buffer *buffer; + struct audio_stream *source; + unsigned int copy_bytes; + unsigned int remaining; + unsigned int total = 0; + void *rptr; + void *dest; + + /* local SOF source buffer */ + buffer = comp_dev_get_first_data_producer(dev); + source = &buffer->stream; + rptr = source->r_ptr; + + /* remote SHM sink buffer */ + dest = plug_ep_wptr(ctx); + + /* maximum byte count that can be copied this iteration */ + remaining = MIN(audio_stream_get_avail_bytes(source), plug_ep_get_free(ctx)); + + while (remaining) { + /* min bytes from source pipe */ + copy_bytes = MIN(remaining, plug_ep_wrap_wsize(ctx)); + + /* min bytes from source and sink */ + copy_bytes = MIN(copy_bytes, audio_stream_bytes_without_wrap(source, rptr)); + + /* anything to copy ? */ + if (copy_bytes == 0) + break; + + /* copy to local buffer from SHM buffer */ + memcpy(dest, rptr, copy_bytes); + + /* update SHM pointer with wrap */ + dest = plug_ep_produce(ctx, copy_bytes); + + /* update local pointers */ + rptr = audio_stream_wrap(source, rptr + copy_bytes); + + /* update avail and totals */ + remaining -= copy_bytes; + total += copy_bytes; + } + + /* update sink buffer pointers */ + comp_update_buffer_consume(buffer, total); + comp_dbg(dev, "wrote %d bytes", total); + + return 0; +} + +/* + * copy to local SOF buffer from remote SHM buffer + */ +static int shmwrite_copy(struct comp_dev *dev) +{ + struct shm_comp_data *cd = comp_get_drvdata(dev); + struct plug_shm_endpoint *ctx = cd->ctx; + struct comp_buffer *buffer; + struct audio_stream *sink; + unsigned int copy_bytes; + unsigned int remaining; + unsigned int total = 0; + void *wptr; + void *src; + + /* local SOF sink buffer */ + buffer = comp_dev_get_first_data_consumer(dev); + sink = &buffer->stream; + wptr = sink->w_ptr; + + /* remote SHM source buffer */ + src = plug_ep_rptr(ctx); + + /* maximum byte count that can be copied this iteration */ + remaining = MIN(audio_stream_get_free_bytes(sink), plug_ep_get_avail(ctx)); + + while (remaining > 0) { + /* min bytes free bytes in local sink */ + copy_bytes = MIN(remaining, plug_ep_wrap_rsize(ctx)); + + /* min bytes to wrap */ + copy_bytes = MIN(copy_bytes, audio_stream_bytes_without_wrap(sink, wptr)); + + /* nothing to copy ? */ + if (copy_bytes == 0) + break; + + /* copy to SHM from local buffer */ + memcpy(wptr, src, copy_bytes); + + /* update local pointers */ + wptr = audio_stream_wrap(sink, wptr + copy_bytes); + + /* update SHM pointer with wrap */ + src = plug_ep_consume(ctx, copy_bytes); + + /* update avail and totals */ + remaining -= copy_bytes; + total += copy_bytes; + } + + /* update sink buffer pointers */ + comp_update_buffer_produce(buffer, total); + comp_dbg(dev, "read %d bytes", total); + + return 0; +} + +static int shm_prepare(struct comp_dev *dev) +{ + struct shm_comp_data *cd = comp_get_drvdata(dev); + struct plug_shm_endpoint *ctx = cd->ctx; + int ret = 0; + + comp_dbg(dev, "shm prepare_copy()"); + + ret = comp_set_state(dev, COMP_TRIGGER_PREPARE); + if (ret < 0) + return ret; + + ctx->state = SOF_PLUGIN_STATE_READY; + + if (ret == COMP_STATUS_STATE_ALREADY_SET) + return PPL_STATUS_PATH_STOP; + + return ret; +} + +static int shm_reset(struct comp_dev *dev) +{ + struct shm_comp_data *cd = comp_get_drvdata(dev); + struct plug_shm_endpoint *ctx = cd->ctx; + + comp_set_state(dev, COMP_TRIGGER_RESET); + ctx->state = SOF_PLUGIN_STATE_INIT; + + return 0; +} + +int shm_get_attribute(struct comp_dev *dev, uint32_t type, void *value) +{ + struct shm_comp_data *cd = comp_get_drvdata(dev); + + switch (type) { + case COMP_ATTR_BASE_CONFIG: + memcpy_s(value, sizeof(struct ipc4_base_module_cfg), + &cd->base_cfg, sizeof(struct ipc4_base_module_cfg)); + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct comp_driver comp_shmread = { + .type = SOF_COMP_HOST, + .uid = SOF_RT_UUID(shmread_uuid), + .tctx = &shmread_tr, + .ops = { + .create = shmread_new, + .free = shm_free, + .params = shmread_params, + .cmd = shm_cmd, + .trigger = shm_trigger, + .copy = shmread_copy, + .prepare = shm_prepare, + .reset = shm_reset, + .get_attribute = shm_get_attribute, + }, +}; + +static const struct comp_driver comp_shmwrite = { + .type = SOF_COMP_HOST, + .uid = SOF_RT_UUID(shmwrite_uuid), + .tctx = &shmwrite_tr, + .ops = { + .create = shmwrite_new, + .free = shm_free, + .params = shmwrite_params, + .cmd = shm_cmd, + .trigger = shm_trigger, + .copy = shmwrite_copy, + .prepare = shm_prepare, + .reset = shm_reset, + .get_attribute = shm_get_attribute, + }, +}; + +static struct comp_driver_info comp_shmread_info = { + .drv = &comp_shmread, +}; + +static struct comp_driver_info comp_shmwrite_info = { + .drv = &comp_shmwrite, +}; + +static void sys_comp_shm_init(void) +{ + comp_register(&comp_shmread_info); + comp_register(&comp_shmwrite_info); +} + +DECLARE_MODULE(sys_comp_shm_init); diff --git a/tools/plugin/pipe/CMakeLists.txt b/tools/plugin/pipe/CMakeLists.txt new file mode 100644 index 000000000000..244fb3199e38 --- /dev/null +++ b/tools/plugin/pipe/CMakeLists.txt @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: BSD-3-Clause + +add_executable(sof-pipe + main.c + cpu.c + pipeline.c + ipc4.c + ../common.c +) + +sof_append_relative_path_definitions(sof-pipe) + +target_include_directories(sof-pipe PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/.. + ${sof_source_directory}/src/audio) + +target_compile_options(sof-pipe PRIVATE -DPIC -g -O3 -Wall -Werror -Wno-stringop-truncation -DCONFIG_LIBRARY -imacros${config_h}) + +target_include_directories(sof-pipe PRIVATE ${sof_install_directory}/include) +target_include_directories(sof-pipe PRIVATE ${parser_install_dir}/include) + +target_link_libraries(sof-pipe PRIVATE -Wl,-whole-archive sof_library -Wl,-no-whole-archive) +target_link_libraries(sof-pipe PRIVATE sof_parser_lib) +target_link_libraries(sof-pipe PRIVATE pthread) +target_link_libraries(sof-pipe PRIVATE -rdynamic -lasound -ldl -lm -lasound -lrt) diff --git a/tools/plugin/pipe/cpu.c b/tools/plugin/pipe/cpu.c new file mode 100644 index 000000000000..ea7e5d0645e2 --- /dev/null +++ b/tools/plugin/pipe/cpu.c @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2022 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood + +/* + * SOF pipeline in userspace. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "pipe.h" + +/* read the CPU ID register data on x86 */ +static inline void x86_cpuid(unsigned int *eax, unsigned int *ebx, + unsigned int *ecx, unsigned int *edx) +{ + /* data type is passed in on eax (and sometimes ecx) */ + asm volatile("cpuid" + : "=a" (*eax), "=b" (*ebx), "=c" (*ecx), "=d" (*edx) + : "0" (*eax), "2" (*ecx)); +} + +/* + * Check core type for E cores. If non hybrid then it does not matter. + */ +static inline int use_this_core(struct sof_pipe *sp) +{ + /* CPUID - set eax to 0x1a for hybrid core types */ + unsigned int eax = 0x1a, ebx = 0, ecx = 0, edx = 0; + char core_mask; + + /* get the processor core type we are running on now */ + x86_cpuid(&eax, &ebx, &ecx, &edx); + + /* core type 0x20 is atom, 0x40 is core */ + core_mask = (eax >> 24) & 0xFF; + switch (core_mask) { + case 0x20: + fprintf(sp->log, "found E core\n"); + if (sp->use_E_core) + return 1; + return 0; + case 0x40: + fprintf(sp->log, "found P core\n"); + if (sp->use_P_core) + return 1; + return 0; + default: + /* non hybrid arch, just use first core */ + fprintf(sp->log, "found non hybrid core topology\n"); + return 1; + } +} + +/* sof-pipe needs to be sticky to the current core for low latency */ +int pipe_set_affinity(struct sof_pipe *sp) +{ + cpu_set_t cpuset; + pthread_t thread; + long core_count = sysconf(_SC_NPROCESSORS_ONLN); + int i; + int err; + + /* Set affinity mask to core */ + thread = pthread_self(); + CPU_ZERO(&cpuset); + + /* find the first E core (usually come after the P cores ?) */ + for (i = core_count - 1; i >= 0; i--) { + CPU_ZERO(&cpuset); + CPU_SET(i, &cpuset); + + /* change our core to i */ + err = pthread_setaffinity_np(thread, sizeof(cpuset), &cpuset); + if (err != 0) { + fprintf(sp->log, "error: failed to set CPU affinity to core %d: %s\n", + i, strerror(err)); + return err; + } + + /* should we use this core ? */ + if (use_this_core(sp)) + break; + } + + return 0; +} + +/* set ipc thread to low priority */ +int pipe_set_ipc_lowpri(struct sof_pipe *sp) +{ + pthread_attr_t attr; + struct sched_param param; + int err; + + /* attempt to set thread priority - needs suid */ + fprintf(sp->log, "pipe: set IPC low priority\n"); + + err = pthread_attr_init(&attr); + if (err < 0) { + fprintf(sp->log, "error: can't create thread attr %d %s\n", err, strerror(errno)); + return err; + } + + err = pthread_attr_setschedpolicy(&attr, SCHED_OTHER); + if (err < 0) { + fprintf(sp->log, "error: can't set thread policy %d %s\n", err, strerror(errno)); + return err; + } + param.sched_priority = 0; + err = pthread_attr_setschedparam(&attr, ¶m); + if (err < 0) { + fprintf(sp->log, "error: can't set thread sched param %d %s\n", + err, strerror(errno)); + return err; + } + + return 0; +} + +/* set pipeline to realtime priority */ +int pipe_set_rt(struct sof_pipe *sp) +{ + pthread_attr_t attr; + struct sched_param param; + int err; + uid_t uid = getuid(); + uid_t euid = geteuid(); + + /* do we have elevated privileges to attempt RT priority */ + if (uid < 0 || uid != euid) { + /* attempt to set thread priority - needs suid */ + fprintf(sp->log, "pipe: set RT priority\n"); + + err = pthread_attr_init(&attr); + if (err < 0) { + fprintf(sp->log, "error: can't create thread attr %d %s\n", + err, strerror(errno)); + return err; + } + + err = pthread_attr_setschedpolicy(&attr, SCHED_FIFO); + if (err < 0) { + fprintf(sp->log, "error: can't set thread policy %d %s\n", + err, strerror(errno)); + return err; + } + param.sched_priority = 80; + err = pthread_attr_setschedparam(&attr, ¶m); + if (err < 0) { + fprintf(sp->log, "error: can't set thread sched param %d %s\n", + err, strerror(errno)); + return err; + } + err = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); + if (err < 0) { + fprintf(sp->log, "error: can't set thread inherit %d %s\n", + err, strerror(errno)); + return err; + } + } else { + fprintf(sp->log, "error: no elevated privileges for RT. uid %d euid %d\n", + uid, euid); + } + + return 0; +} diff --git a/tools/plugin/pipe/ipc4.c b/tools/plugin/pipe/ipc4.c new file mode 100644 index 000000000000..90157971d05e --- /dev/null +++ b/tools/plugin/pipe/ipc4.c @@ -0,0 +1,426 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2022 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood + +/* + * SOF pipeline in userspace. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "pipe.h" + +// TODO: take prefix from ALSA prefix +#define COMP_PREFIX "./sof_ep/install/lib/libsof_" +#define COMP_SUFFIX ".so" +#define UUID_STR_SIZE 32 + +struct sof_pipe_module_library_map { + int module_id; + const char *name; +}; + +static const struct sof_pipe_module_library_map library_map[] = { + {0x6, "libsof_volume.so"}, + {0x2, "libsof_mixer.so"}, + {0x3, "libsof_mixer.so"}, + {0x95, "libsof_ns.so"}, + /*FIXME: hack for now to set up ALSA and SHM components */ + {0x96, "libsof_mod_shm.so"}, /* host playback */ + {0x97, "libsof_mod_alsa.so"}, /* dai playback */ + {0x98, "libsof_mod_shm.so"}, /* host capture */ + {0x99, "libsof_mod_alsa.so"}, /* dai capture */ +}; + +static int pipe_register_comp(struct sof_pipe *sp, uint16_t module_id) +{ + const struct sof_pipe_module_library_map *lib; + int i; + + /* check if module already loaded */ + for (i = 0; i < sp->mod_idx; i++) { + if (sp->module[i].module_id == module_id) + return 0; /* module found and already loaded */ + } + + for (i = 0; i < ARRAY_SIZE(library_map); i++) { + lib = &library_map[i]; + + if (module_id == lib->module_id) + break; + } + + if (i == ARRAY_SIZE(library_map)) { + fprintf(stderr, "module ID: %d not supported\n", module_id); + return -ENOTSUP; + } + + /* not loaded, so load module */ + sp->module[sp->mod_idx].handle = dlopen(lib->name, RTLD_NOW); + if (!sp->module[sp->mod_idx].handle) { + fprintf(stderr, "error: cant load module %s: %s\n", + lib->name, dlerror()); + return -errno; + } + + sp->mod_idx++; + + return 0; +} + +#define iCS(x) ((x) & SOF_CMD_TYPE_MASK) +#define iGS(x) ((x) & SOF_GLB_TYPE_MASK) + +static int pipe_sof_ipc_cmd_before(struct sof_pipe *sp, void *mailbox, size_t bytes) +{ + struct ipc4_message_request *in = mailbox; + enum ipc4_message_target target = in->primary.r.msg_tgt; + int ret = 0; + + switch (target) { + case SOF_IPC4_MESSAGE_TARGET_MODULE_MSG: + { + uint32_t type = in->primary.r.type; + + switch (type) { + case SOF_IPC4_MOD_INIT_INSTANCE: + { + struct ipc4_module_init_instance *module_init = + (struct ipc4_module_init_instance *)in; + + ret = pipe_register_comp(sp, module_init->primary.r.module_id); + break; + } + default: + break; + } + break; + } + case SOF_IPC4_MESSAGE_TARGET_FW_GEN_MSG: + { + uint32_t type = in->primary.r.type; + + switch (type) { + case SOF_IPC4_GLB_SET_PIPELINE_STATE: + { + struct ipc4_pipeline_set_state *state; + struct ipc_comp_dev *ipc_pipe; + struct ipc *ipc = ipc_get(); + unsigned int pipeline_id; + + state = (struct ipc4_pipeline_set_state *)in; + pipeline_id = (unsigned int)state->primary.r.ppl_id; + + if (state->primary.r.ppl_state == SOF_IPC4_PIPELINE_STATE_PAUSED) { + ipc_pipe = ipc_get_pipeline_by_id(ipc, pipeline_id); + if (!ipc_pipe) { + fprintf(stderr, "No pipeline with instance_id = %u", + pipeline_id); + return -EINVAL; + } + + /* stop the pipeline thread */ + ret = pipe_thread_stop(sp, ipc_pipe->pipeline); + if (ret < 0) { + printf("error: can't start pipeline %d thread\n", + ipc_pipe->pipeline->comp_id); + return ret; + } + } + break; + } + default: + break; + } + break; + } + default: + fprintf(sp->log, "ipc: unknown command target %u size %ld", + target, bytes); + ret = -EINVAL; + break; + } + + return ret; +} + +static int pipe_sof_ipc_cmd_after(struct sof_pipe *sp, void *mailbox, size_t bytes) +{ + struct ipc4_message_request *in = mailbox; + enum ipc4_message_target target = in->primary.r.msg_tgt; + int ret = 0; + + switch (target) { + case SOF_IPC4_MESSAGE_TARGET_MODULE_MSG: + break; + case SOF_IPC4_MESSAGE_TARGET_FW_GEN_MSG: + { + uint32_t type = in->primary.r.type; + + switch (type) { + case SOF_IPC4_GLB_CREATE_PIPELINE: + { + struct ipc4_pipeline_create *pipe_desc = (struct ipc4_pipeline_create *)in; + struct ipc_comp_dev *ipc_pipe; + struct ipc *ipc = ipc_get(); + unsigned int pipeline_id = (unsigned int)pipe_desc->primary.r.instance_id; + + ipc_pipe = ipc_get_pipeline_by_id(ipc, pipeline_id); + if (!ipc_pipe) { + fprintf(stderr, "No pipeline with instance_id = %u\n", + pipeline_id); + return -EINVAL; + } + + /* create new pipeline thread */ + ret = pipe_thread_new(sp, ipc_pipe->pipeline); + if (ret < 0) { + printf("error: can't create pipeline %d thread\n", + ipc_pipe->pipeline->pipeline_id); + return ret; + } + break; + } + case SOF_IPC4_GLB_SET_PIPELINE_STATE: + { + struct ipc4_pipeline_set_state *state; + struct ipc_comp_dev *ipc_pipe; + struct ipc *ipc = ipc_get(); + unsigned int pipeline_id; + + state = (struct ipc4_pipeline_set_state *)in; + pipeline_id = (unsigned int)state->primary.r.ppl_id; + + if (state->primary.r.ppl_state == SOF_IPC4_PIPELINE_STATE_RUNNING) { + ipc_pipe = ipc_get_pipeline_by_id(ipc, pipeline_id); + if (!ipc_pipe) { + fprintf(stderr, "No pipeline with instance_id = %u\n", + pipeline_id); + return -EINVAL; + } + + /* start the pipeline thread */ + ret = pipe_thread_start(sp, ipc_pipe->pipeline); + if (ret < 0) { + printf("error: can't start pipeline %d thread\n", + ipc_pipe->pipeline->comp_id); + return ret; + } + } + break; + } + case SOF_IPC4_GLB_DELETE_PIPELINE: + { + struct ipc4_pipeline_create *pipe_desc = (struct ipc4_pipeline_create *)in; + unsigned int pipeline_id = (unsigned int)pipe_desc->primary.r.instance_id; + + /* free pipeline thread */ + ret = pipe_thread_free(sp, pipeline_id); + if (ret < 0) { + printf("error: can't free pipeline %d thread\n", + pipeline_id); + return ret; + } + + break; + } + default: + break; + } + break; + } + default: + fprintf(sp->log, "ipc: unknown command target %u size %ld", + target, bytes); + ret = -EINVAL; + break; + } + + return ret; +} + +int pipe_ipc_do(struct sof_pipe *sp, void *mailbox, size_t bytes) +{ + char mailbox_copy[IPC3_MAX_MSG_SIZE] = {0}; + int err = 0; + + /* preserve mailbox contents for local "after" config */ + memcpy(mailbox_copy, mailbox, bytes); + + /* some IPCs require pipe to perform actions before core */ + /* mailbox can be re-written here by local pipe if needed */ + err = pipe_sof_ipc_cmd_before(sp, mailbox, bytes); + if (err < 0) { + fprintf(sp->log, "error: local IPC processing failed\n"); + return err; + } + + /* is the IPC local only or do we need send to infra ? */ + err = pipe_ipc_message(sp, mailbox, bytes); + if (err < 0) { + fprintf(sp->log, "error: infra IPC processing failed\n"); + return err; + } + + /* some IPCs require pipe to perform actions before core */ + err = pipe_sof_ipc_cmd_after(sp, mailbox_copy, bytes); + if (err < 0) { + fprintf(sp->log, "error: local IPC processing failed\n"); + return err; + } + + return err; +} + +static void *handle_ipc_client(void *data) +{ + struct sof_pipe *sp = data; + struct plug_socket_desc *ipc_socket = &sp->ipc_socket; + struct timespec ts; + int client_id = sp->new_client_id; + int clientfd = sp->client_sock_ids[client_id]; + char mailbox[IPC3_MAX_MSG_SIZE] = {0}; + ssize_t ipc_size; + int err; + + while (1) { + memset(mailbox, 0, IPC3_MAX_MSG_SIZE); + + ipc_size = recv(clientfd, mailbox, IPC3_MAX_MSG_SIZE, 0); + if (ipc_size < 0) { + fprintf(sp->log, "error: can't read IPC socket %s : %s\n", + ipc_socket->path, strerror(errno)); + break; + } + + /* connection lost */ + if (ipc_size == 0) { + close(clientfd); + break; + } + + /* TODO: properly validate message and continue if garbage */ + if (*((uint32_t *)mailbox) == 0) { + fprintf(sp->log, "sof-pipe: IPC %s garbage read\n", ipc_socket->path); + ts.tv_sec = 0; + ts.tv_nsec = 20 * 1000 * 1000; /* 20 ms */ + nanosleep(&ts, NULL); + continue; + } + + /* do the message work */ + //data_dump(mailbox, IPC3_MAX_MSG_SIZE); + err = pipe_ipc_do(sp, mailbox, ipc_size); + if (err < 0) + fprintf(sp->log, "error: local IPC processing failed\n"); + + /* now return message completion status found in mailbox */ + err = send(clientfd, mailbox, IPC3_MAX_MSG_SIZE, 0); + if (err < 0) { + close(clientfd); + break; + } + } + + close(clientfd); + sp->client_sock_ids[client_id] = 0; + return NULL; +} + +int pipe_ipc_process(struct sof_pipe *sp, struct plug_socket_desc *ipc_socket) +{ + pthread_t thread_id; + int err; + + /* IPC thread should not preempt processing thread */ + err = pipe_set_ipc_lowpri(sp); + if (err < 0) + fprintf(sp->log, "error: cant set PCM IPC thread to low priority"); + + /* create the IPC socket */ + err = plug_socket_create(ipc_socket); + if (err < 0) { + fprintf(sp->log, "error: can't create TX IPC socket : %s\n", + strerror(errno)); + return -errno; + } + + /* let main() know we are ready */ + fprintf(sp->log, "sof-pipe: IPC %s socket ready\n", ipc_socket->path); + + /* main PCM IPC handling loop */ + while (1) { + int clientfd, i; + + /* Accept a connection from a client */ + clientfd = accept(ipc_socket->socket_fd, NULL, NULL); + if (clientfd == -1) { + fprintf(sp->log, "IPC %s socket accept failed\n", ipc_socket->path); + return -errno; + } + + for (i = 0; i < MAX_IPC_CLIENTS; i++) { + if (!sp->client_sock_ids[i]) { + sp->client_sock_ids[i] = clientfd; + sp->new_client_id = i; + break; + } + } + + /* create a new thread for each new IPC client */ + if (pthread_create(&thread_id, NULL, handle_ipc_client, sp) != 0) { + fprintf(sp->log, "Client IPC thread creation failed"); + close(clientfd); + sp->client_sock_ids[i] = 0; + continue; + } + + fprintf(sp->log, "Client IPC thread created client id %d\n", clientfd); + /* Detach the thread */ + pthread_detach(thread_id); + } + + close(ipc_socket->socket_fd); + + fprintf(sp->log, "***sof-pipe: IPC %s thread finished !!\n", ipc_socket->path); + return 0; +} diff --git a/tools/plugin/pipe/main.c b/tools/plugin/pipe/main.c new file mode 100644 index 000000000000..68861fc0af2a --- /dev/null +++ b/tools/plugin/pipe/main.c @@ -0,0 +1,345 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2022 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood + +/* + * SOF pipeline in userspace. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "pipe.h" + +#define VERSION "v0.1" + +/* global needed for signal handler */ +struct sof_pipe *_sp; + +static void shutdown(struct sof_pipe *sp) +{ + struct pipethread_data *pipeline_ctx = sp->pipeline_ctx; + int i; + + /* free everything */ + munmap(sp->shm_context.addr, sp->shm_context.size); + shm_unlink(sp->shm_context.name); + + /* cancel all threads, free locks and message queues */ + for (i = 0; i < sp->pipe_thread_count; i++) { + struct pipethread_data *pd = &pipeline_ctx[i]; + + pthread_cancel(pd->ipc_thread); + pthread_cancel(pd->pcm_thread); + plug_lock_free(&pd->ready); + plug_lock_free(&pd->done); + } + + /* free the sof-pipe IPC tx/rx message queues */ + plug_socket_free(&sp->ipc_socket); + + pthread_mutex_destroy(&sp->ipc_lock); + + fflush(sp->log); + fflush(stdout); + fflush(stderr); +} + +/* signals from the ALSA PCM plugin or something has gone wrong */ +static void signal_handler(int sig) +{ + switch (sig) { + case SIGTERM: + fprintf(_sp->log, "Pipe caught SIGTERM - shutdown\n"); + break; + case SIGINT: + fprintf(_sp->log, "Pipe caught SIGINT - shutdown\n"); + break; + default: + fprintf(_sp->log, "Pipe caught signal %d, something went wrong\n", sig); + break; + } + fprintf(_sp->log, "Pipe shutdown signal\n"); + + /* try and clean up if we can */ + shutdown(_sp); + exit(EXIT_FAILURE); +} + +static int pipe_init_signals(struct sof_pipe *sp) +{ + struct sigaction *action = &sp->action; + int err; + + /* + * signals - currently only check for SIGCHLD. TODO: handle more + */ + sigemptyset(&action->sa_mask); + action->sa_handler = signal_handler; + err = sigaction(SIGTERM, action, NULL); + if (err < 0) { + fprintf(sp->log, "failed to register signal action: %s", + strerror(errno)); + return err; + } + + err = sigaction(SIGSEGV, action, NULL); + if (err < 0) { + fprintf(sp->log, "failed to register signal action: %s", + strerror(errno)); + return err; + } + + err = sigaction(SIGINT, action, NULL); + if (err < 0) { + fprintf(sp->log, "failed to register signal action: %s", + strerror(errno)); + return err; + } + + return 0; +} + +int pipe_ipc_message(struct sof_pipe *sp, void *mailbox, size_t bytes) +{ + struct ipc *ipc = ipc_get(); + + /* reply is copied back to mailbox */ + pthread_mutex_lock(&sp->ipc_lock); + memcpy(ipc->comp_data, mailbox, bytes); + ipc_cmd(mailbox); + memcpy(mailbox, ipc->comp_data, bytes); + pthread_mutex_unlock(&sp->ipc_lock); + + return 0; +} + +/* + * Create and open a new semaphore using the lock object. + */ +int plug_lock_create(struct plug_sem_desc *lock) +{ + /* delete any old stale resources that use our resource name */ + sem_unlink(lock->name); + + /* RW blocking lock */ + lock->sem = sem_open(lock->name, O_CREAT | O_RDWR | O_EXCL, SEM_PERMS, 0); + if (lock->sem == SEM_FAILED) { + SNDERR("failed to create semaphore %s: %s", lock->name, strerror(errno)); + return -errno; + } + + return 0; +} + +/* + * Free and delete semaphore resourses in lock object. + */ +void plug_lock_free(struct plug_sem_desc *lock) +{ + sem_close(lock->sem); + sem_unlink(lock->name); +} + +/* + * Create and open a new shared memory region using the SHM object. + */ +int plug_shm_create(struct plug_shm_desc *shm) +{ + int err; + + /* delete any old stale resources that use our resource name */ + shm_unlink(shm->name); + + /* open SHM to be used for low latency position */ + shm->fd = shm_open(shm->name, O_RDWR | O_CREAT, S_IRWXU | S_IRWXG); + if (shm->fd < 0) { + SNDERR("failed to create SHM position %s: %s", shm->name, strerror(errno)); + return -errno; + } + + /* set SHM size */ + err = ftruncate(shm->fd, shm->size); + if (err < 0) { + SNDERR("failed to truncate SHM position %s: %s", shm->name, strerror(errno)); + shm_unlink(shm->name); + return -errno; + } + + /* map it locally for context readback */ + shm->addr = mmap(NULL, shm->size, PROT_READ | PROT_WRITE, MAP_SHARED, shm->fd, 0); + if (!shm->addr) { + SNDERR("failed to mmap SHM position%s: %s", shm->name, strerror(errno)); + shm_unlink(shm->name); + return -errno; + } + + return 0; +} + +/* + * Free and delete shared memory region resourses in SHM object. + */ +void plug_shm_free(struct plug_shm_desc *shm) +{ + close(shm->fd); + shm_unlink(shm->name); +} + + +/* + * Free and delete message queue resources in IPC object. + */ +void plug_socket_free(struct plug_socket_desc *ipc) +{ + unlink(ipc->path); +} + +/* + * -D ALSA device. e.g. + * -R realtime (needs parent to set uid) + * -p Force run on P core + * -e Force run on E core + * -t topology name. + * -L log file (otherwise stdout) + * -h help + */ +static void usage(char *name) +{ + fprintf(stdout, "Usage: %s -D ALSA device -T topology\n", name); +} + +int main(int argc, char *argv[], char *env[]) +{ + struct sof_pipe sp = {0}; + int option = 0; + int ret = 0; + + /* default config */ + sp.log = stdout; + sp.alsa_name = "default"; /* default sound device */ + _sp = &sp; + + /* parse all args */ + while ((option = getopt(argc, argv, "hD:RpeT:")) != -1) { + switch (option) { + /* Alsa device */ + case 'D': + sp.alsa_name = strdup(optarg); + break; + case 'R': + sp.realtime = 1; + break; + case 'p': + sp.use_P_core = 1; + sp.use_E_core = 0; + break; + case 'e': + sp.use_E_core = 1; + sp.use_P_core = 0; + break; + case 'T': + snprintf(sp.topology_name, NAME_SIZE, "%s", optarg); + break; + + /* print usage */ + default: + fprintf(sp.log, "unknown option %c\n", option); + __attribute__ ((fallthrough)); + case 'h': + usage(argv[0]); + exit(EXIT_SUCCESS); + } + } + + /* validate cmd line params */ + if (strlen(sp.topology_name) == 0) { + fprintf(sp.log, "error: no IPC topology name specified\n"); + exit(EXIT_FAILURE); + } + + /* global IPC access serialisation mutex */ + ret = pthread_mutex_init(&sp.ipc_lock, NULL); + if (ret < 0) { + fprintf(sp.log, "error: cant create mutex %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + + fprintf(sp.log, "sof-pipe-%s: using topology %s\n", VERSION, sp.topology_name); + + /* set CPU affinity */ + if (sp.use_E_core || sp.use_P_core) { + ret = pipe_set_affinity(&sp); + if (ret < 0) + goto out; + } + + /* initialize ipc and scheduler */ + if (pipe_sof_setup(sof_get()) < 0) { + fprintf(stderr, "error: pipeline init\n"); + exit(EXIT_FAILURE); + } + + /* global context - plugin clients open this first */ + ret = plug_shm_init(&sp.shm_context, sp.topology_name, "ctx", 0); + if (ret < 0) + goto out; + + /* cleanup any lingering global IPC files */ + shm_unlink(sp.shm_context.name); + + /* make sure we can cleanly shutdown */ + ret = pipe_init_signals(&sp); + if (ret < 0) + goto out; + + /* mmap context on successful topology load */ + ret = plug_shm_create(&sp.shm_context); + if (ret < 0) + goto out; + + /* now prep the global context for client plugin access */ + sp.glb = sp.shm_context.addr; + memset(sp.glb, 0, sizeof(*sp.glb)); + sprintf(sp.glb->magic, "%s", SOF_MAGIC); + sp.glb->size = sizeof(*sp.glb); + sp.glb->state = SOF_PLUGIN_STATE_INIT; + sp.tplg.tplg_file = sp.topology_name; + sp.tplg.ipc_major = 4; //HACK hard code to v4 + + /* sofpipe is now ready */ + sp.glb->state = SOF_PLUGIN_STATE_INIT; + + ret = plug_socket_path_init(&sp.ipc_socket, "sof", "ipc", 0); + if (ret < 0) + goto out; + + /* now process IPCs as they arrive from plugins */ + ret = pipe_ipc_process(&sp, &sp.ipc_socket); + +out: + fprintf(sp.log, "shutdown main\n"); + shutdown(&sp); + return ret; +} diff --git a/tools/plugin/pipe/pipe.h b/tools/plugin/pipe/pipe.h new file mode 100644 index 000000000000..8f8fb26080ea --- /dev/null +++ b/tools/plugin/pipe/pipe.h @@ -0,0 +1,125 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2022-2023 Intel Corporation. All rights reserved. + * + * Author: Liam Girdwood + */ + +#ifndef __SOF_PLUGIN_PIPE_H__ +#define __SOF_PLUGIN_PIPE_H__ + +#include +#include +#include +#include + +#include +#include +#include +#include "common.h" + +struct sof_pipe; + +#define MAX_MODULE_ID 256 +#define MAX_PIPE_THREADS 128 +#define MAX_PIPELINES 32 + +struct pipethread_data { + pthread_t pcm_thread; + pthread_t ipc_thread; + struct sof_pipe *sp; + struct pipeline *pcm_pipeline; + /* PCM flow control */ + struct plug_sem_desc ready; + struct plug_sem_desc done; + atomic_int pipe_users; +}; + +struct sof_pipe_module { + void *handle; + char uuid[SOF_UUID_SIZE]; + int module_id; +}; + +struct sof_pipe { + const char *alsa_name; + char topology_name[NAME_SIZE]; + int realtime; + int use_P_core; + int use_E_core; + int capture; + int file_mode; + int pipe_thread_count; + + struct sigaction action; + + /* SHM for stream context sync */ + struct plug_shm_desc shm_context; + struct plug_shm_glb_state *glb; + + FILE *log; + pthread_mutex_t ipc_lock; + struct tplg_context tplg; + struct tplg_comp_info *comp_list; + struct list_item widget_list; /* list of widgets */ + struct list_item route_list; /* list of widget connections */ + struct list_item pcm_list; /* list of PCMs */ + int info_elems; + int info_index; + + /* IPC */ + pthread_t ipc_pcm_thread; + struct plug_socket_desc ipc_socket; /* queue used by plugin to send IPCs */ + int client_sock_ids[MAX_IPC_CLIENTS]; + int client_count; + int new_client_id; + + /* module SO handles */ + struct sof_pipe_module module[MAX_MODULE_ID]; + int mod_idx; + + /* pipeline context */ + struct pipethread_data pipeline_ctx[MAX_PIPELINES]; +}; + +/* global needed for signal handler */ +extern struct sof_pipe *_sp; + +int pipe_thread_new(struct sof_pipe *sp, struct pipeline *p); +int pipe_thread_free(struct sof_pipe *sp, int pipeline_id); +int pipe_thread_start(struct sof_pipe *sp, struct pipeline *p); +int pipe_thread_stop(struct sof_pipe *sp, struct pipeline *p); +int pipe_sof_setup(struct sof *sof); +int pipe_kcontrol_cb_new(struct snd_soc_tplg_ctl_hdr *tplg_ctl, + void *comp, void *arg); + +/* set pipeline to realtime priority */ +int pipe_set_rt(struct sof_pipe *sp); + +/* set ipc thread to low priority */ +int pipe_set_ipc_lowpri(struct sof_pipe *sp); + +int pipe_ipc_process(struct sof_pipe *sp, struct plug_socket_desc *ipc_socket); + +int pipe_ipc_new(struct sof_pipe *sp, int pri, int core); +void pipe_ipc_free(struct sof_pipe *sp); + +int pipe_set_affinity(struct sof_pipe *sp); + +int pipe_ipc_message(struct sof_pipe *sp, void *mailbox, size_t bytes); + +int pipe_ipc_do(struct sof_pipe *sp, void *mailbox, size_t bytes); + +/* + * Topology + */ +int plug_parse_topology(struct sof_pipe *sp, struct tplg_context *ctx); + +int plug_load_widget(struct sof_pipe *sp, struct tplg_context *ctx); + +int plug_register_graph(struct sof_pipe *sp, struct tplg_context *ctx, + struct tplg_comp_info *temp_comp_list, + char *pipeline_string, + int count, int num_comps, int pipeline_id); + +#endif diff --git a/tools/plugin/pipe/pipeline.c b/tools/plugin/pipe/pipeline.c new file mode 100644 index 000000000000..2985e46d04ad --- /dev/null +++ b/tools/plugin/pipe/pipeline.c @@ -0,0 +1,356 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2022 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood + +/* + * SOF pipeline in userspace. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "pipe.h" + +#define MAX_PIPE_USERS 8 + +static struct ll_schedule_domain domain = {0}; + +// TODO: all these steps are probably not needed - i.e we only need IPC and pipeline. +int pipe_sof_setup(struct sof *sof) +{ + /* register modules */ + + domain.next_tick = 0; + + /* init components */ + sys_comp_init(sof); + + /* other necessary initializations, todo: follow better SOF init */ + pipeline_posn_init(sof); + init_system_notify(sof); + + /* init IPC */ + if (ipc_init(sof) < 0) { + fprintf(stderr, "error: IPC init\n"); + return -EINVAL; + } + + /* 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; + } + + return 0; +} + +static inline int pipe_copy_ready(struct pipethread_data *pd) +{ + struct timespec delay; + int err; + + /* get the current time for source delay */ + err = clock_gettime(CLOCK_REALTIME, &delay); + if (err == -1) { + fprintf(_sp->log, "shm: cant get time: %s", strerror(errno)); + return -errno; + } + + // TODO get from rate + plug_timespec_add_ms(&delay, 2000); + + /* wait for data from source */ + err = sem_timedwait(pd->ready.sem, &delay); + if (err == -1) { + fprintf(_sp->log, "%s %d: fatal timeout: %s on %s\n", __FILE__, __LINE__, + strerror(errno), pd->ready.name); + return -errno; + } + + return 0; +} + +static inline void pipe_copy_done(struct pipethread_data *pd) +{ + /* tell peer we are done */ + sem_post(pd->done.sem); +} + +static void *pipe_process_thread(void *arg) +{ + struct pipethread_data *pd = arg; + int err; + + fprintf(_sp->log, "pipe thread started for pipeline %d\n", + pd->pcm_pipeline->pipeline_id); + + do { + if (pd->pcm_pipeline->status != COMP_STATE_ACTIVE) { + fprintf(_sp->log, "pipe state non active %d\n", + pd->pcm_pipeline->status); + break; + } + + if (pd->pipe_users <= 0) { + fprintf(_sp->log, "pipe no users.\n"); + break; + } + + /* wait for pipe to be ready */ + err = pipe_copy_ready(pd); + if (err < 0) { + fprintf(_sp->log, "pipe ready timeout on pipeline %d state %d users %d\n", + pd->pcm_pipeline->pipeline_id, pd->pcm_pipeline->status, + pd->pipe_users); + break; + } + + /* sink has read data so now generate more it */ + err = pipeline_copy(pd->pcm_pipeline); + + pipe_copy_done(pd); + + if (err < 0) { + fprintf(_sp->log, "pipe thread error %d\n", err); + break; + } else if (err > 0) { + fprintf(_sp->log, "pipe thread complete %d\n", err); + break; + } + + } while (1); + + fprintf(_sp->log, "pipe complete for pipeline %d\n", + pd->pcm_pipeline->pipeline_id); + return 0; +} + +static void *pipe_ipc_process_thread(void *arg) +{ + struct pipethread_data *pd = arg; + int err; + + /* initialise semaphores to default starting value */ + err = sem_init(pd->done.sem, 1, 0); + if (err < 0) { + fprintf(_sp->log, "failed to reset DONE: %s\n", + strerror(errno)); + return NULL; + } + err = sem_init(pd->ready.sem, 1, 0); + if (err < 0) { + fprintf(_sp->log, "failed to reset READY: %s\n", + strerror(errno)); + return NULL; + } + + return NULL; +} + +int pipe_thread_start(struct sof_pipe *sp, struct pipeline *p) +{ + struct pipethread_data *pipeline_ctx = sp->pipeline_ctx; + struct pipethread_data *pd; + int pipeline_id; + int ret, pipe_users; + + pipeline_id = p->pipeline_id; + + if (pipeline_id >= MAX_PIPELINES) { + fprintf(_sp->log, "error: pipeline ID %d out of range\n", pipeline_id); + return -EINVAL; + } + + if (!pipeline_ctx[pipeline_id].sp) { + fprintf(_sp->log, "error: pipeline ID %d not in use\n", pipeline_id); + return -EINVAL; + } + pd = &pipeline_ctx[pipeline_id]; + + /* only create thread if not active */ + pipe_users = atomic_fetch_add(&pd->pipe_users, 1); + if (pipe_users > 0) { + fprintf(_sp->log, "pipeline ID %d thread already running %d users\n", + pipeline_id, pipe_users); + return 0; + } + + fprintf(_sp->log, "pipeline ID %d thread not running so starting...\n", pipeline_id); + + /* first user so start the PCM pipeline thread */ + ret = pthread_create(&pd->pcm_thread, NULL, pipe_process_thread, pd); + if (ret < 0) { + fprintf(_sp->log, "failed to create PCM thread: %s\n", strerror(errno)); + return -errno; + } + + return ret; +} + +int pipe_thread_stop(struct sof_pipe *sp, struct pipeline *p) +{ + struct pipethread_data *pipeline_ctx = sp->pipeline_ctx; + struct pipethread_data *pd; + int pipeline_id; + int ret, pipe_users; + + pipeline_id = p->pipeline_id; + + /* this is called when the pipeline is PAUSED for the first time before RUNNING */ + if (p->status == COMP_STATE_INIT) + return 0; + + if (pipeline_id >= MAX_PIPELINES) { + fprintf(_sp->log, "error: pipeline ID %d out of range\n", pipeline_id); + return -EINVAL; + } + + if (!pipeline_ctx[pipeline_id].sp) { + fprintf(_sp->log, "error: pipeline ID %d not in use\n", pipeline_id); + return -EINVAL; + } + pd = &pipeline_ctx[pipeline_id]; + + /* only join thread if not active */ + pipe_users = atomic_fetch_sub(&pd->pipe_users, 1); + if (pipe_users != 1) { + fprintf(_sp->log, "pipeline ID %d thread has multiple %d users\n", + pipeline_id, pipe_users); + return 0; + } + + fprintf(_sp->log, "pipeline ID %d thread can be stopped...\n", pipeline_id); + + ret = pthread_cancel(pd->pcm_thread); + if (ret < 0) { + fprintf(_sp->log, "failed to cancel PCM thread: %s\n", strerror(errno)); + return -errno; + } + + return ret; +} + +int pipe_thread_new(struct sof_pipe *sp, struct pipeline *p) +{ + struct pipethread_data *pipeline_ctx = sp->pipeline_ctx; + struct pipethread_data *pd; + int ret; + + if (!p) { + fprintf(_sp->log, "error: invalid pipeline\n"); + return -EINVAL; + } + + if (p->pipeline_id >= MAX_PIPELINES) { + fprintf(_sp->log, "error: pipeline ID %d out of range\n", p->pipeline_id); + return -EINVAL; + } + + if (pipeline_ctx[p->pipeline_id].sp) { + fprintf(_sp->log, "error: pipeline ID %d in use\n", p->pipeline_id); + return -EINVAL; + } + pd = &pipeline_ctx[p->pipeline_id]; + pd->sp = _sp; + pd->pcm_pipeline = p; + + /* init names of shared resources */ + ret = plug_lock_init(&pd->ready, _sp->topology_name, "ready", p->pipeline_id); + if (ret < 0) + return ret; + + ret = plug_lock_init(&pd->done, _sp->topology_name, "done", p->pipeline_id); + if (ret) + return ret; + + /* open semaphores */ + ret = plug_lock_create(&pd->ready); + if (ret < 0) + return ret; + ret = plug_lock_create(&pd->done); + if (ret < 0) + goto lock_err; + + /* start IPC pipeline thread */ + ret = pthread_create(&pd->ipc_thread, NULL, pipe_ipc_process_thread, pd); + if (ret < 0) { + fprintf(_sp->log, "failed to create IPC thread: %s\n", strerror(errno)); + ret = -errno; + goto lock2_err; + } + + return 0; + +lock2_err: + plug_lock_free(&pd->done); +lock_err: + plug_lock_free(&pd->ready); + return ret; +} + +int pipe_thread_free(struct sof_pipe *sp, int pipeline_id) +{ + struct pipethread_data *pipeline_ctx = sp->pipeline_ctx; + struct pipethread_data *pd; + int err; + + if (pipeline_id >= MAX_PIPELINES) { + fprintf(_sp->log, "error: pipeline ID %d out of range\n", pipeline_id); + return -EINVAL; + } + + if (!pipeline_ctx[pipeline_id].sp) { + fprintf(_sp->log, "error: pipeline ID %d not in use\n", pipeline_id); + return -EINVAL; + } + + pd = &pipeline_ctx[pipeline_id]; + + err = pthread_cancel(pd->ipc_thread); + if (err < 0) { + fprintf(_sp->log, "failed to create IPC thread: %s\n", strerror(errno)); + return -errno; + } + + plug_lock_free(&pd->ready); + plug_lock_free(&pd->done); + + pd->sp = NULL; + return 0; +} diff --git a/tools/probes/CMakeLists.txt b/tools/probes/CMakeLists.txt index 21f6f2decfb9..1b39e6d192db 100644 --- a/tools/probes/CMakeLists.txt +++ b/tools/probes/CMakeLists.txt @@ -3,8 +3,8 @@ cmake_minimum_required(VERSION 3.13) add_executable(sof-probes + probes_demux.c probes_main.c - ../../src/math/numbers.c ) target_compile_options(sof-probes PRIVATE @@ -15,4 +15,9 @@ target_include_directories(sof-probes PRIVATE "../../src/include" ) +# TODO: probes should not need to include RTOS headers. FIX. +target_include_directories(sof-probes PRIVATE + "../../xtos/include" +) + install(TARGETS sof-probes DESTINATION bin) diff --git a/tools/probes/probes_demux.c b/tools/probes/probes_demux.c new file mode 100644 index 000000000000..a411eef117b9 --- /dev/null +++ b/tools/probes/probes_demux.c @@ -0,0 +1,338 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2022 Intel Corporation. All rights reserved. +// +// Author: Adrian Bonislawski +// Jyri Sarha (restructured and moved to this file) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "wave.h" + +#define APP_NAME "sof-probes" + +#define PACKET_MAX_SIZE 4096 /**< Size limit for probe data packet */ +#define DATA_READ_LIMIT 4096 /**< Data limit for file read */ +#define FILES_LIMIT 32 /**< Maximum num of probe output files */ +#define FILE_PATH_LIMIT 128 /**< Path limit for probe output files */ + +struct wave_files { + FILE *fd; + uint32_t buffer_id; + uint32_t fmt; + uint32_t size; + struct wave header; +}; + +enum p_state { + READY = 0, /**< At this stage app is looking for a SYNC word */ + SYNC, /**< SYNC received, copying data */ + CHECK /**< Check crc and save packet if valid */ +}; + +struct dma_frame_parser { + bool log_to_stdout; + enum p_state state; + struct probe_data_packet *packet; + size_t packet_size; + uint8_t *w_ptr; /* Write pointer to copy data to */ + uint32_t total_data_to_copy; /* Total bytes left to copy */ + int start; /* Start of unfilled data */ + int len; /* Data buffer fill level */ + uint8_t data[DATA_READ_LIMIT]; + struct wave_files files[FILES_LIMIT]; +}; + +static uint32_t sample_rate[] = { + 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, + 48000, 64000, 88200, 96000, 128000, 176400, 192000 +}; + +int get_buffer_file(struct wave_files *files, uint32_t buffer_id) +{ + int i; + + for (i = 0; i < FILES_LIMIT; i++) { + if (files[i].fd != NULL && files[i].buffer_id == buffer_id) + return i; + } + return -1; +} + +int get_buffer_file_free(struct wave_files *files) +{ + int i; + + for (i = 0; i < FILES_LIMIT; i++) { + if (files[i].fd == NULL) + return i; + } + return -1; +} + +bool is_audio_format(uint32_t format) +{ + return (format & PROBE_MASK_FMT_TYPE) != 0 && (format & PROBE_MASK_AUDIO_FMT) == 0; +} + +int init_wave(struct dma_frame_parser *p, uint32_t buffer_id, uint32_t format) +{ + bool audio = is_audio_format(format); + char path[FILE_PATH_LIMIT]; + int i; + + i = get_buffer_file_free(p->files); + if (i == -1) { + fprintf(stderr, "error: too many buffers\n"); + exit(0); + } + + sprintf(path, "buffer_%d.%s", buffer_id, audio ? "wav" : "bin"); + + fprintf(stderr, "%s:\t Creating file %s\n", APP_NAME, path); + + if (!audio && p->log_to_stdout) { + p->files[i].fd = stdout; + } else { + p->files[i].fd = fopen(path, "wb"); + if (!p->files[i].fd) { + fprintf(stderr, "error: unable to create file %s, error %d\n", + path, errno); + exit(0); + } + } + + p->files[i].buffer_id = buffer_id; + p->files[i].fmt = format; + + if (!audio) + return i; + + p->files[i].header.riff.chunk_id = HEADER_RIFF; + p->files[i].header.riff.format = HEADER_WAVE; + p->files[i].header.fmt.subchunk_id = HEADER_FMT; + p->files[i].header.fmt.subchunk_size = 16; + p->files[i].header.fmt.audio_format = 1; + p->files[i].header.fmt.num_channels = ((format & PROBE_MASK_NB_CHANNELS) >> PROBE_SHIFT_NB_CHANNELS) + 1; + p->files[i].header.fmt.sample_rate = sample_rate[(format & PROBE_MASK_SAMPLE_RATE) >> PROBE_SHIFT_SAMPLE_RATE]; + p->files[i].header.fmt.bits_per_sample = (((format & PROBE_MASK_CONTAINER_SIZE) >> PROBE_SHIFT_CONTAINER_SIZE) + 1) * 8; + p->files[i].header.fmt.byte_rate = p->files[i].header.fmt.sample_rate * + p->files[i].header.fmt.num_channels * + p->files[i].header.fmt.bits_per_sample / 8; + p->files[i].header.fmt.block_align = p->files[i].header.fmt.num_channels * + p->files[i].header.fmt.bits_per_sample / 8; + p->files[i].header.data.subchunk_id = HEADER_DATA; + + fwrite(&p->files[i].header, sizeof(struct wave), 1, p->files[i].fd); + + return i; +} + +void finalize_wave_files(struct dma_frame_parser *p) +{ + struct wave_files *files = p->files; + uint32_t i, chunk_size; + + /* fill the header at the beginning of each file */ + /* and close all opened files */ + /* check wave struct to understand the offsets */ + for (i = 0; i < FILES_LIMIT; i++) { + if (!is_audio_format(files[i].fmt)) + continue; + + if (files[i].fd) { + chunk_size = files[i].size + sizeof(struct wave) - + offsetof(struct riff_chunk, format); + + fseek(files[i].fd, sizeof(uint32_t), SEEK_SET); + fwrite(&chunk_size, sizeof(uint32_t), 1, files[i].fd); + fseek(files[i].fd, sizeof(struct wave) - + offsetof(struct data_subchunk, subchunk_size), + SEEK_SET); + fwrite(&files[i].size, sizeof(uint32_t), 1, files[i].fd); + + fclose(files[i].fd); + } + } +} + +int validate_data_packet(struct probe_data_packet *packet) +{ + uint64_t *checksump; + uint64_t sum; + + sum = (uint32_t) (packet->sync_word + + packet->buffer_id + + packet->format + + packet->timestamp_high + + packet->timestamp_low + + packet->data_size_bytes); + + checksump = (uint64_t *) (packet->data + packet->data_size_bytes); + + if (sum != *checksump) { + fprintf(stderr, "Checksum error 0x%016" PRIx64 " != 0x%016" PRIx64 "\n", + sum, *checksump); + return -EINVAL; + } + + return 0; +} + +int process_sync(struct dma_frame_parser *p) +{ + struct probe_data_packet *temp_packet; + + /* request to copy data_size from probe packet and 64-bit checksum */ + p->total_data_to_copy = p->packet->data_size_bytes + sizeof(uint64_t); + + if (sizeof(struct probe_data_packet) + p->total_data_to_copy > + p->packet_size) { + p->packet_size = sizeof(struct probe_data_packet) + + p->total_data_to_copy; + + temp_packet = realloc(p->packet, p->packet_size); + + if (!temp_packet) + return -ENOMEM; + + p->packet = temp_packet; + } + + p->w_ptr = (uint8_t *)p->packet->data; + + return 0; +} + +struct dma_frame_parser *parser_init(void) +{ + struct dma_frame_parser *p = malloc(sizeof(*p)); + if (!p) { + fprintf(stderr, "error: allocation failed, err %d\n", + errno); + return NULL; + } + memset(p, 0, sizeof(*p)); + p->packet = malloc(PACKET_MAX_SIZE); + if (!p) { + fprintf(stderr, "error: allocation failed, err %d\n", + errno); + free(p); + return NULL; + } + memset(p->packet, 0, PACKET_MAX_SIZE); + p->packet_size = PACKET_MAX_SIZE; + return p; +} + +void parser_free(struct dma_frame_parser *p) +{ + free(p->packet); + free(p); +} + +void parser_log_to_stdout(struct dma_frame_parser *p) +{ + p->log_to_stdout = true; +} + +void parser_fetch_free_buffer(struct dma_frame_parser *p, uint8_t **d, size_t *len) +{ + *d = &p->data[p->start]; + *len = sizeof(p->data) - p->start; +} + +int parser_parse_data(struct dma_frame_parser *p, size_t d_len) +{ + uint i = 0; + + p->len = p->start + d_len; + /* processing all loaded bytes */ + while (i < p->len) { + if (p->total_data_to_copy == 0) { + switch (p->state) { + case READY: + /* check for SYNC */ + if (p->len - i < sizeof(p->packet->sync_word)) { + p->start = p->len - i; + memmove(&p->data[0], &p->data[i], p->start); + i += p->start; + } else if (*((uint32_t *)&p->data[i]) == + PROBE_EXTRACT_SYNC_WORD) { + memset(p->packet, 0, p->packet_size); + /* request to copy full data packet */ + p->total_data_to_copy = + sizeof(struct probe_data_packet); + p->w_ptr = (uint8_t *)p->packet; + p->state = SYNC; + p->start = 0; + } else { + i++; + } + break; + case SYNC: + /* SYNC -> CHECK */ + if (process_sync(p) < 0) { + fprintf(stderr, "OOM, quitting\n"); + return -ENOMEM; + } + p->state = CHECK; + break; + case CHECK: + /* CHECK -> READY */ + /* find corresponding file and save data if valid */ + if (validate_data_packet(p->packet) == 0) { + int file = get_buffer_file(p->files, + p->packet->buffer_id); + + if (file < 0) + file = init_wave(p, p->packet->buffer_id, + p->packet->format); + + if (file < 0) { + fprintf(stderr, + "unable to open file for %u\n", + p->packet->buffer_id); + return -EIO; + } + + fwrite(p->packet->data, 1, + p->packet->data_size_bytes, + p->files[file].fd); + p->files[file].size += p->packet->data_size_bytes; + } + p->state = READY; + break; + } + } + /* data copying section */ + if (p->total_data_to_copy > 0) { + uint data_to_copy; + + /* check if there is enough bytes loaded */ + /* or copy partially if not */ + if (i + p->total_data_to_copy > p->len) { + data_to_copy = p->len - i; + p->total_data_to_copy -= data_to_copy; + } else { + data_to_copy = p->total_data_to_copy; + p->total_data_to_copy = 0; + } + memcpy(p->w_ptr, &p->data[i], data_to_copy); + p->w_ptr += data_to_copy; + i += data_to_copy; + } + } + return 0; +} diff --git a/tools/probes/probes_demux.h b/tools/probes/probes_demux.h new file mode 100644 index 000000000000..93aa89119a5e --- /dev/null +++ b/tools/probes/probes_demux.h @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2022 Intel Corporation. All rights reserved. +// +// Author: Jyri Sarha +// + +#ifndef _PROBES_DEMUX_H_ +#define _PROBES_DEMUX_H_ + +#include +#include +#include + +struct dma_frame_parser; + +struct dma_frame_parser *parser_init(void); + +void parser_log_to_stdout(struct dma_frame_parser *p); + +void parser_free(struct dma_frame_parser *p); + +void parser_fetch_free_buffer(struct dma_frame_parser *p, uint8_t **d, size_t *len); + +int parser_parse_data(struct dma_frame_parser *p, size_t d_len); + +void finalize_wave_files(struct dma_frame_parser *p); + +#endif diff --git a/tools/probes/probes_main.c b/tools/probes/probes_main.c index 6bb35ce943ff..cdba2c6347d5 100644 --- a/tools/probes/probes_main.c +++ b/tools/probes/probes_main.c @@ -13,316 +13,86 @@ * */ -#include -#include -#include "wave.h" - -#include #include -#include #include #include #include #include #include -#include #include -#define APP_NAME "sof-probes" - -#define PACKET_MAX_SIZE 4096 /**< Size limit for probe data packet */ -#define DATA_READ_LIMIT 1024 /**< Data limit for file read */ -#define FILES_LIMIT 32 /**< Maximum num of probe output files */ -#define FILE_PATH_LIMIT 128 /**< Path limit for probe output files */ - -struct wave_files { - FILE *fd; - uint32_t buffer_id; - uint32_t size; - struct wave header; -}; +#include "probes_demux.h" -enum p_state { - READY = 0, /**< At this stage app is looking for a SYNC word */ - SYNC, /**< SYNC received, copying data */ - CHECK /**< Check crc and save packet if valid */ -}; - -static uint32_t sample_rate[] = { - 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, - 48000, 64000, 88200, 96000, 128000, 176400, 192000 -}; +#define APP_NAME "sof-probes" static void usage(void) { fprintf(stdout, "Usage %s \n\n", APP_NAME); fprintf(stdout, "%s:\t -p file\tParse extracted file\n\n", APP_NAME); + fprintf(stdout, "%s:\t -l \t\tLog to stdout\n\n", APP_NAME); fprintf(stdout, "%s:\t -h \t\tHelp, usage info\n", APP_NAME); exit(0); } -int write_data(char *path, char *data) -{ - FILE *fd; - - fd = fopen(path, "w"); - if (!fd) { - fprintf(stderr, "error: unable to open file %s, error %d\n", - path, errno); - return errno; - } - - fprintf(fd, "%s", data); - fclose(fd); - - return 0; -} - -int get_buffer_file(struct wave_files *files, uint32_t buffer_id) -{ - int i; - - for (i = 0; i < FILES_LIMIT; i++) { - if (files[i].buffer_id == buffer_id) - return i; - } - return -1; -} - -int init_wave(struct wave_files *files, uint32_t buffer_id, uint32_t format) +void parse_data(const char *file_in, bool log_to_stdout) { - char path[FILE_PATH_LIMIT]; - int i; - - i = get_buffer_file(files, 0); - if (i == -1) { - fprintf(stderr, "error: too many buffers\n"); - exit(0); - } - - fprintf(stdout, "%s:\t Creating wave file for buffer id: %d\n", - APP_NAME, buffer_id); - - sprintf(path, "buffer_%d.wav", buffer_id); + struct dma_frame_parser *p = parser_init(); + FILE *fd_in; + uint8_t *data; + size_t len; + int ret; - files[i].fd = fopen(path, "wb"); - if (!files[i].fd) { - fprintf(stderr, "error: unable to create file %s, error %d\n", - path, errno); - exit(0); + if (!p) { + fprintf(stderr, "parser_init() failed\n"); + exit(1); } - files[i].buffer_id = buffer_id; - - files[i].header.riff.chunk_id = HEADER_RIFF; - files[i].header.riff.format = HEADER_WAVE; - files[i].header.fmt.subchunk_id = HEADER_FMT; - files[i].header.fmt.subchunk_size = 16; - files[i].header.fmt.audio_format = 1; - files[i].header.fmt.num_channels = ((format & PROBE_MASK_NB_CHANNELS) >> PROBE_SHIFT_NB_CHANNELS) + 1; - files[i].header.fmt.sample_rate = sample_rate[(format & PROBE_MASK_SAMPLE_RATE) >> PROBE_SHIFT_SAMPLE_RATE]; - files[i].header.fmt.bits_per_sample = (((format & PROBE_MASK_CONTAINER_SIZE) >> PROBE_SHIFT_CONTAINER_SIZE) + 1) * 8; - files[i].header.fmt.byte_rate = files[i].header.fmt.sample_rate * - files[i].header.fmt.num_channels * - files[i].header.fmt.bits_per_sample / 8; - files[i].header.fmt.block_align = files[i].header.fmt.num_channels * - files[i].header.fmt.bits_per_sample / 8; - files[i].header.data.subchunk_id = HEADER_DATA; - - fwrite(&files[i].header, sizeof(struct wave), 1, files[i].fd); - - return i; -} - -void finalize_wave_files(struct wave_files *files) -{ - uint32_t i, chunk_size; - - /* fill the header at the beginning of each file */ - /* and close all opened files */ - /* check wave struct to understand the offsets */ - for (i = 0; i < FILES_LIMIT; i++) { - if (files[i].fd) { - chunk_size = files[i].size + sizeof(struct wave) - - offsetof(struct riff_chunk, format); - - fseek(files[i].fd, sizeof(uint32_t), SEEK_SET); - fwrite(&chunk_size, sizeof(uint32_t), 1, files[i].fd); - fseek(files[i].fd, sizeof(struct wave) - - offsetof(struct data_subchunk, subchunk_size), - SEEK_SET); - fwrite(&files[i].size, sizeof(uint32_t), 1, files[i].fd); + if (log_to_stdout) + parser_log_to_stdout(p); - fclose(files[i].fd); + if (file_in) { + fd_in = fopen(file_in, "rb"); + if (!fd_in) { + fprintf(stderr, "error: unable to open file %s, error %d\n", + file_in, errno); + exit(0); } - } -} - -int validate_data_packet(struct probe_data_packet *data_packet) -{ - uint32_t received_crc; - uint32_t calc_crc; - - received_crc = data_packet->checksum; - data_packet->checksum = 0; - calc_crc = crc32(0, (char *)data_packet, sizeof(*data_packet)); - - if (received_crc == calc_crc) { - return 0; } else { - fprintf(stderr, "error: data packet for buffer %d is not valid: crc32: %d/%d\n", - data_packet->buffer_id, calc_crc, received_crc); - return -EINVAL; - } -} - -int process_sync(struct probe_data_packet *packet, uint32_t **w_ptr, uint32_t *total_data_to_copy) -{ - struct probe_data_packet *temp_packet; - - /* request to copy data_size from probe packet */ - *total_data_to_copy = packet->data_size_bytes / - sizeof(uint32_t); - if (packet->data_size_bytes > PACKET_MAX_SIZE) { - temp_packet = realloc(packet, - sizeof(struct probe_data_packet) + packet->data_size_bytes); - if (!temp_packet) - return -ENOMEM; - } - - *w_ptr = (uint32_t *)&packet->data; - return 0; -} - -void parse_data(char *file_in) -{ - FILE *fd_in; - struct wave_files files[FILES_LIMIT]; - struct probe_data_packet *packet; - uint32_t data[DATA_READ_LIMIT]; - uint32_t total_data_to_copy = 0; - uint32_t data_to_copy = 0; - uint32_t *w_ptr; - int i, j, file; - - enum p_state state = READY; - - fprintf(stdout, "%s:\t Parsing file: %s\n", APP_NAME, file_in); - - fd_in = fopen(file_in, "rb"); - if (!fd_in) { - fprintf(stderr, "error: unable to open file %s, error %d\n", - file_in, errno); - exit(0); + fd_in = stdin; } - packet = malloc(PACKET_MAX_SIZE); - if (!packet) { - fprintf(stderr, "error: allocation failed, err %d\n", - errno); - fclose(fd_in); - exit(0); - } - memset(&data, 0, sizeof(uint32_t) * DATA_READ_LIMIT); - memset(&files, 0, sizeof(struct wave_files) * FILES_LIMIT); - - /* data read loop to process DATA_READ_LIMIT bytes at each iteration */ do { - i = fread(&data, sizeof(uint32_t), DATA_READ_LIMIT, fd_in); - /* processing all loaded bytes */ - for (j = 0; j < i; j++) { - /* SYNC received */ - if (data[j] == PROBE_EXTRACT_SYNC_WORD) { - if (state != READY) { - fprintf(stderr, "error: wrong state %d, err %d\n", - state, errno); - free(packet); - exit(0); - } - memset(packet, 0, PACKET_MAX_SIZE); - /* request to copy full data packet */ - total_data_to_copy = sizeof(struct probe_data_packet) / - sizeof(uint32_t); - w_ptr = (uint32_t *)packet; - state = SYNC; - } - /* data copying section */ - if (total_data_to_copy > 0) { - /* check if there is enough bytes loaded */ - /* or copy partially if not */ - if (j + total_data_to_copy > i) { - data_to_copy = i - j; - total_data_to_copy -= data_to_copy; - } else { - data_to_copy = total_data_to_copy; - total_data_to_copy = 0; - } - memcpy(w_ptr, data + j, data_to_copy * sizeof(uint32_t)); - w_ptr += data_to_copy; - j += data_to_copy - 1; - } - - if (total_data_to_copy == 0) { - switch (state) { - case READY: - break; - case SYNC: - /* SYNC -> CHECK */ - if (process_sync(packet, &w_ptr, &total_data_to_copy) < 0) { - fprintf(stderr, "OOM, quitting\n"); - goto err; - } - state = CHECK; - break; - case CHECK: - /* CHECK -> READY */ - /* find corresponding file and save data if valid */ - if (validate_data_packet(packet) == 0) { - file = get_buffer_file(files, - packet->buffer_id); - - if (file < 0) - file = init_wave(files, - packet->buffer_id, - packet->format); + parser_fetch_free_buffer(p, &data, &len); + len = fread(data, 1, len, fd_in); + ret = parser_parse_data(p, len); + } while (!ret && !feof(fd_in)); - fwrite(packet->data, - sizeof(uint32_t), - packet->data_size_bytes / - sizeof(uint32_t), - files[file].fd); + if (!log_to_stdout) + finalize_wave_files(p); - files[file].size += packet->data_size_bytes; - } - state = READY; - break; - } - } - } - } while (i > 0); - -err: - /* all done, can close files */ - finalize_wave_files(files); - free(packet); - fclose(fd_in); - fprintf(stdout, "%s:\t done\n", APP_NAME); } int main(int argc, char *argv[]) { + const char *fname = NULL; + bool log_to_stdout = false; int opt; - while ((opt = getopt(argc, argv, "hp:")) != -1) { + while ((opt = getopt(argc, argv, "lhp:")) != -1) { switch (opt) { case 'p': - parse_data(optarg); + fname = optarg; + break; + case 'l': + log_to_stdout = true; break; case 'h': default: usage(); + return 0; } } + parse_data(fname, log_to_stdout); return 0; } diff --git a/tools/rimage/.checkpatch.conf b/tools/rimage/.checkpatch.conf new file mode 100644 index 000000000000..98ddafcb3fb1 --- /dev/null +++ b/tools/rimage/.checkpatch.conf @@ -0,0 +1,6 @@ +--codespell +--codespellfile scripts/spelling.txt +--ignore C99_COMMENT_TOLERANCE +--no-tree +--strict +-g diff --git a/tools/rimage/CMakeLists.txt b/tools/rimage/CMakeLists.txt new file mode 100644 index 000000000000..8e0a5da7d267 --- /dev/null +++ b/tools/rimage/CMakeLists.txt @@ -0,0 +1,67 @@ +# SPDX-License-Identifier: BSD-3-Clause + +cmake_minimum_required(VERSION 3.10) + +project(SOF_RIMAGE C) + +if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + message(STATUS "No CMAKE_BUILD_TYPE, defaulting to Debug") + set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Build Type" FORCE) +endif() + +add_executable(rimage + src/file_simple.c + src/cse.c + src/css.c + src/plat_auth.c + src/hash.c + src/pkcs1_5.c + src/manifest.c + src/ext_manifest.c + src/rimage.c + src/toml_utils.c + src/adsp_config.c + src/misc_utils.c + src/file_utils.c + src/elf_file.c + src/module.c + tomlc99/toml.c +) + +set_property(TARGET rimage PROPERTY C_STANDARD 99) + +target_compile_options(rimage PRIVATE + -Wall -Werror -Wmissing-prototypes -Wimplicit-fallthrough +) + +if(CMAKE_C_COMPILER_VERSION VERSION_GREATER 9.1) + target_compile_options(rimage PRIVATE -Wno-char-subscripts) +endif() + +# Windows builds use MSYS2 https://www.msys2.org/ to get linux tools and headers. +# MSYS_INSTALL_DIR variable points to MSYS2 installation directory. +# You may pass it as environmental or cmake configure variable. +if(${CMAKE_HOST_WIN32}) + cmake_minimum_required(VERSION 3.20) + if(DEFINED ENV{MSYS_INSTALL_DIR} AND NOT MSYS_INSTALL_DIR) + set(MSYS_INSTALL_DIR $ENV{MSYS_INSTALL_DIR}) + endif() + + if(MSYS_INSTALL_DIR) + cmake_path(IS_ABSOLUTE MSYS_INSTALL_DIR IS_MSYS_INSTALL_DIR_ABSOLUTE) + if(NOT IS_MSYS_INSTALL_DIR_ABSOLUTE) + message(FATAL_ERROR "Please provide absolute path to MSYS2 installation + setting MSYS_INSTALL_DIR env variable") + endif() + # Include standard posix headers. Requires pacman openssl-devel package. + cmake_path(APPEND MSYS_INSTALL_DIR "usr" "include" OUTPUT_VARIABLE MSYS_SYSTEM_INCLUDE_PATH) + target_include_directories(rimage PRIVATE "${MSYS_SYSTEM_INCLUDE_PATH}") + endif() +endif() + +target_link_libraries(rimage PRIVATE crypto) + +target_include_directories(rimage PRIVATE + src/include/ + tomlc99/ +) diff --git a/tools/rimage/LICENSE b/tools/rimage/LICENSE new file mode 100644 index 000000000000..468893dff94b --- /dev/null +++ b/tools/rimage/LICENSE @@ -0,0 +1,97 @@ +/* + * BSD 3 Clause + * Copyright (c) 2016, Intel Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + + +// Copyright (c) 2003-2014 Cadence Design Systems, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +Files with 2-Clause BSD licence: + +src/include/rimage/elf.h + +/* + * Derived from: + * $FreeBSD: src/sys/sys/elf32.h,v 1.8.14.1 2005/12/30 22:13:58 marcel Exp $ + * $FreeBSD: src/sys/sys/elf64.h,v 1.10.14.1 2005/12/30 22:13:58 marcel Exp $ + * $FreeBSD: src/sys/sys/elf_common.h,v 1.15.8.1 2005/12/30 22:13:58 marcel Exp $ + * $FreeBSD: src/sys/alpha/include/elf.h,v 1.14 2003/09/25 01:10:22 peter Exp $ + * $FreeBSD: src/sys/amd64/include/elf.h,v 1.18 2004/08/03 08:21:48 dfr Exp $ + * $FreeBSD: src/sys/arm/include/elf.h,v 1.5.2.1 2006/06/30 21:42:52 cognet Exp $ + * $FreeBSD: src/sys/i386/include/elf.h,v 1.16 2004/08/02 19:12:17 dfr Exp $ + * $FreeBSD: src/sys/powerpc/include/elf.h,v 1.7 2004/11/02 09:47:01 ssouhlal Exp $ + * $FreeBSD: src/sys/sparc64/include/elf.h,v 1.12 2003/09/25 01:10:26 peter Exp $ + * + * Copyright (c) 1996-1998 John D. Polstra. All rights reserved. + * Copyright (c) 2001 David E. O'Brien + * Portions Copyright 2009 The Go Authors. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ diff --git a/tools/rimage/README.md b/tools/rimage/README.md new file mode 100644 index 000000000000..9f32fffa5cb1 --- /dev/null +++ b/tools/rimage/README.md @@ -0,0 +1,105 @@ +# rimage + +`rimage` is a DSP firmware image creation and signing tool targeting +the DSP on certain Intel System-on-Chip (SoC). This is used by +the [Sound Open Firmware (SOF)](https://github.com/thesofproject/sof) +to generate binary image files. + +## Building + +Most SOF users never build `rimage` directly but as an ExternalProject +defined by CMake in SOF. This makes sure they always use an up-to-date +version of rimage and configuration files that have been fully tested. + +If needed, `rimage` can be built manually with the usual CMake commands: + +```shell +$ cmake -B build/ +$ make -C build/ help # lists all targets +$ make -C build/ +``` + +The `build/rimage` executable can then be copied to a directory in the +PATH. Zephyr users can run `west config rimage.path +/path/to/rimage/build/rimage`; Zephyr documentation and `west sign -h` +have more details. + +## Testing tomlc99 changes with SOF Continuous Integration + +This section is about leveraging SOF validation to test tomlc99 changes +_before_ submitting them to the tomlc99 repository. + +Nothing here is actually specific to SOF and tomlc99; you can apply the +same test logic to any submodule and parent on Github. In fact the same +logic applies to submodule alternatives. Github is the only requirement. + +### Get familiar with git submodules + +This is unfortunately not optional for SOF and tomlc99. + +For various reasons submodules seem to confuse many git users. Maybe +because the versions of the submodules are not directly visible in some +configuration file like with most alternatives? Either way, an +unfortunate prerequisite before doing any tomlc99 work is to get familiar +with git submodules in general. As submodules are built-in there are +many resources about them on the Internet. One possible starting point +is https://git-scm.com/book/en/v2/Git-Tools-Submodules but feel free +to use any other good tutorial instead. Make sure you actually practice +a tutorial; don't just read it. Practicing on a temporary and throw-away +copy of SOF + tomlc99 is a great idea. + +Obviously, you also need to be familiar with regular Github pull +requests. + +### Run SOF tests on unmerged tomlc99 commits + +First, push the tomlc99 commits you want to be tested to any branch of +your tomlc99 fork on Github. Do _not_ submit an tomlc99 pull request yet. + +Note your tomlc99 fork must have been created using the actual "fork" +button on Github so Github is aware of the connection with the upstream +tomlc99 repo. In the top-left corner you should see `forked from +thesofproject/tomlc99` under the name of your fork. If not then search +the Internet for "re-attach detached github fork". + +Then, **pretend** these tomlc99 commits have already been accepted and +merged (they have been neither) and submit to SOF a draft pull request +that updates the main SOF branch with your brand new tomlc99 commits to +test. The only SOF commit in this SOF TEST pull request is an SOF commit +that updates the tomlc99 pointer to the SHA of your last tomlc99 +commit. If you're not sure how to do this then you must go back to the +previous section and practice submodules more. + +Submit this SOF pull request as a Github _draft_ so reviewers are _not_ +notified. Starting every pull request as a draft is always a good idea +but in this case this particular SOF pull request can be especially +confusing because it points at commits in a different repo and commits +that are not merged yet. So you _really_ don't want to bother busy +reviewers (here's a secret: some of the reviewers don't like submodules +either). You can freely switch back and forth between draft and ready +status and should indeed switch to draft if you forgot at submission +time but you can never "un-notify" reviewers. + +Github has very good support for submodules and will display your SOF +TEST pull request better than what the git command line can show. For +instance Github will list your tomlc99 changes directly in the SOF Pull +Request. So if something looks unexpected on Github then it means you +did something wrong. Stop immediately (except for switching to draft if +you forgot) and ask the closest git guru for help. + +Search for "Submodule" in the build logs and make sure the last of your +new tomlc99 commits has been checked out. + +Iterate and force-push your tomlc99 branch and your SOF TEST pull request +until all the SOF tests pass. Then you can submit your tomlc99 pull +request as usual. In the comments section of the tomlc99 pull request, +point at your test results on the SOF side to impress the tomlc99 +reviewers and get your tomlc99 changes merged faster. + +Finally, after your tomlc99 changes have been merged, you can if you want +submit one final SOF pull request that points to the final tomlc99 +SHA. Or, if your tomlc99 change is not urgently needed, you can just wait +for someone else to do it later. If you do it, copy the tomlc99 git log +--oneline in the SOF commit message. Find some good (and less good) +commit message examples for submodule updates at +https://github.com/thesofproject/sof/commits/main/rimage diff --git a/tools/rimage/config/acp_6_0.toml b/tools/rimage/config/acp_6_0.toml new file mode 100644 index 000000000000..f761b839399f --- /dev/null +++ b/tools/rimage/config/acp_6_0.toml @@ -0,0 +1,15 @@ +version = [1, 0] # use simple file write + +[adsp] +name = "acp_6_0" + +[[adsp.mem_zone]] +type = "IRAM" +base = "0x7F000000" +size = "0x40000" +host_offset = "0x0" +[[adsp.mem_zone]] +type = "DRAM" +base = "0xE0000000" +size = "0xE0000" +host_offset = "0x0" diff --git a/tools/rimage/config/acp_6_3.toml b/tools/rimage/config/acp_6_3.toml new file mode 100644 index 000000000000..8a6286dde153 --- /dev/null +++ b/tools/rimage/config/acp_6_3.toml @@ -0,0 +1,20 @@ +version = [1, 0] # use simple file write + +[adsp] +name = "acp_6_3" + +[[adsp.mem_zone]] +type = "IRAM" +base = "0x7F000000" +size = "0x60000" +host_offset = "0x0" +[[adsp.mem_zone]] +type = "DRAM" +base = "0xE0000000" +size = "0x20000" +host_offset = "0x0" +[[adsp.mem_zone]] +type = "SRAM" +base = "0x60006000" +size = "0x27A000" +host_offset = "0x0" diff --git a/tools/rimage/config/acp_7_0.toml b/tools/rimage/config/acp_7_0.toml new file mode 100644 index 000000000000..916bcb24f047 --- /dev/null +++ b/tools/rimage/config/acp_7_0.toml @@ -0,0 +1,20 @@ +version = [1, 0] # use simple file write + +[adsp] +name = "acp_7_0" + +[[adsp.mem_zone]] +type = "IRAM" +base = "0x7F000000" +size = "0x60000" +host_offset = "0x0" +[[adsp.mem_zone]] +type = "DRAM" +base = "0xE0000000" +size = "0x20000" +host_offset = "0x0" +[[adsp.mem_zone]] +type = "SRAM" +base = "0x6000C000" +size = "0x274000" +host_offset = "0x0" \ No newline at end of file diff --git a/tools/rimage/config/apl.toml b/tools/rimage/config/apl.toml new file mode 100644 index 000000000000..e100c78a754c --- /dev/null +++ b/tools/rimage/config/apl.toml @@ -0,0 +1,57 @@ +version = [1, 8] + +[adsp] +name = "apl" +image_size = "0x0A0000" # (8 + 2) bank * 64KB +alias_mask = "0xE0000000" + +[[adsp.mem_zone]] +type = "ROM" +base = "0xBEFE0000" +size = "0x00002000" +[[adsp.mem_zone]] +type = "SRAM" +base = "0xA000A000" +size = "0x100000" + +[[adsp.mem_alias]] +type = "uncached" +base = "0x9E000000" +[[adsp.mem_alias]] +type = "cached" +base = "0xBE000000" + +[cse] +partition_name = "ADSP" +[[cse.entry]] +name = "ADSP.man" +offset = "0x58" +length = "0x378" +[[cse.entry]] +name = "cavs0015.met" +offset = "0x400" +length = "0x60" +[[cse.entry]] +name = "cavs0015" +offset = "0x480" +length = "0x0" # calculated by rimage + +[css] + +[signed_pkg] +name = "ADSP" +[[signed_pkg.module]] +name = "cavs0015.met" + +[partition_info] +name = "ADSP" +[[partition_info.module]] +name = "cavs0015.met" + +[adsp_file] +[[adsp_file.comp]] +base_offset = "0x2000" + +[fw_desc.header] +name = "ADSPFW" +load_offset = "0x2000" diff --git a/tools/rimage/config/bdw.toml b/tools/rimage/config/bdw.toml new file mode 100644 index 000000000000..6b642fcd882e --- /dev/null +++ b/tools/rimage/config/bdw.toml @@ -0,0 +1,15 @@ +version = [1, 0] # use simple file write + +[adsp] +name = "bdw" + +[[adsp.mem_zone]] +type = "IRAM" +base = "0" +size = "0x50000" +host_offset = "0x000A0000" +[[adsp.mem_zone]] +type = "DRAM" +base = "0x00400000" +size = "0xA0000" +host_offset = "0x0" diff --git a/tools/rimage/config/bsw.toml b/tools/rimage/config/bsw.toml new file mode 100644 index 000000000000..6746ca6cff34 --- /dev/null +++ b/tools/rimage/config/bsw.toml @@ -0,0 +1,15 @@ +version = [1, 0] # use simple file write + +[adsp] +name = "bsw" + +[[adsp.mem_zone]] +type = "IRAM" +base = "0xFF2C0000" +size = "0x14000" +host_offset = "0x0C0000" +[[adsp.mem_zone]] +type = "DRAM" +base = "0xFF300000" +size = "0x28000" +host_offset = "0x100000" diff --git a/tools/rimage/config/byt.toml b/tools/rimage/config/byt.toml new file mode 100644 index 000000000000..4afd1c0dd523 --- /dev/null +++ b/tools/rimage/config/byt.toml @@ -0,0 +1,15 @@ +version = [1, 0] # use simple file write + +[adsp] +name = "byt" + +[[adsp.mem_zone]] +type = "IRAM" +base = "0xFF2C0000" +size = "0x14000" +host_offset = "0x0C0000" +[[adsp.mem_zone]] +type = "DRAM" +base = "0xFF300000" +size = "0x28000" +host_offset = "0x100000" diff --git a/tools/rimage/config/cht.toml b/tools/rimage/config/cht.toml new file mode 100644 index 000000000000..2ed88370d16a --- /dev/null +++ b/tools/rimage/config/cht.toml @@ -0,0 +1,15 @@ +version = [1, 0] # use simple file write + +[adsp] +name = "cht" + +[[adsp.mem_zone]] +type = "IRAM" +base = "0xFF2C0000" +size = "0x14000" +host_offset = "0x0C0000" +[[adsp.mem_zone]] +type = "DRAM" +base = "0xFF300000" +size = "0x28000" +host_offset = "0x100000" diff --git a/tools/rimage/config/cnl.toml b/tools/rimage/config/cnl.toml new file mode 100644 index 000000000000..ccf33fb68e2a --- /dev/null +++ b/tools/rimage/config/cnl.toml @@ -0,0 +1,61 @@ +version = [1, 8] + +[adsp] +name = "cnl" +image_size = "0x300000" # (47 + 1) bank * 64KB +alias_mask = "0xE0000000" + +[[adsp.mem_zone]] +type = "ROM" +base = "0xBEFE0000" +size = "0x00002000" +[[adsp.mem_zone]] +type = "IMR" +base = "0xB0038000" +size = "0x100000" +[[adsp.mem_zone]] +type = "SRAM" +base = "0xBE040000" +size = "0x100000" + +[[adsp.mem_alias]] +type = "uncached" +base = "0x9E000000" +[[adsp.mem_alias]] +type = "cached" +base = "0xBE000000" + +[cse] +partition_name = "ADSP" +[[cse.entry]] +name = "ADSP.man" +offset = "0x58" +length = "0x378" +[[cse.entry]] +name = "cavs0015.met" +offset = "0x400" +length = "0x60" +[[cse.entry]] +name = "cavs0015" +offset = "0x480" +length = "0x0" # calculated by rimage + +[css] + +[signed_pkg] +name = "ADSP" +[[signed_pkg.module]] +name = "cavs0015.met" + +[partition_info] +name = "ADSP" +[[partition_info.module]] +name = "cavs0015.met" + +[adsp_file] +[[adsp_file.comp]] +base_offset = "0x2000" + +[fw_desc.header] +name = "ADSPFW" +load_offset = "0x30000" diff --git a/tools/rimage/config/hsw.toml b/tools/rimage/config/hsw.toml new file mode 100644 index 000000000000..2f96a675b89d --- /dev/null +++ b/tools/rimage/config/hsw.toml @@ -0,0 +1,15 @@ +version = [1, 0] # use simple file write + +[adsp] +name = "hsw" + +[[adsp.mem_zone]] +type = "IRAM" +base = "0" +size = "0x60000" +host_offset = "0x80000" +[[adsp.mem_zone]] +type = "DRAM" +base = "0x00400000" +size = "0x80000" +host_offset = "0x0" diff --git a/tools/rimage/config/icl.toml b/tools/rimage/config/icl.toml new file mode 100644 index 000000000000..f6e0fc9cb209 --- /dev/null +++ b/tools/rimage/config/icl.toml @@ -0,0 +1,61 @@ +version = [1, 8] + +[adsp] +name = "icl" +image_size = "0x300000" # (47 + 1) bank * 64KB +alias_mask = "0xE0000000" + +[[adsp.mem_zone]] +type = "ROM" +base = "0xBEFE0000" +size = "0x00002000" +[[adsp.mem_zone]] +type = "IMR" +base = "0xB0038000" +size = "0x100000" +[[adsp.mem_zone]] +type = "SRAM" +base = "0xBE040000" +size = "0x100000" + +[[adsp.mem_alias]] +type = "uncached" +base = "0x9E000000" +[[adsp.mem_alias]] +type = "cached" +base = "0xBE000000" + +[cse] +partition_name = "ADSP" +[[cse.entry]] +name = "ADSP.man" +offset = "0x58" +length = "0x378" +[[cse.entry]] +name = "cavs0015.met" +offset = "0x400" +length = "0x60" +[[cse.entry]] +name = "cavs0015" +offset = "0x480" +length = "0x0" # calculated by rimage + +[css] + +[signed_pkg] +name = "ADSP" +[[signed_pkg.module]] +name = "cavs0015.met" + +[partition_info] +name = "ADSP" +[[partition_info.module]] +name = "cavs0015.met" + +[adsp_file] +[[adsp_file.comp]] +base_offset = "0x2000" + +[fw_desc.header] +name = "ADSPFW" +load_offset = "0x30000" diff --git a/tools/rimage/config/imx8.toml b/tools/rimage/config/imx8.toml new file mode 100644 index 000000000000..865be902e840 --- /dev/null +++ b/tools/rimage/config/imx8.toml @@ -0,0 +1,20 @@ +version = [1, 0] # use simple file write + +[adsp] +name = "imx8" + +[[adsp.mem_zone]] +type = "IRAM" +base = "0x596F8000" +size = "0x800" +host_offset = "0x10000" +[[adsp.mem_zone]] +type = "DRAM" +base = "0x596E8000" +size = "0x8000" +host_offset = "0x0" +[[adsp.mem_zone]] +type = "SRAM" +base = "0x92400000" +size = "0x800000" +host_offset = "0x0" diff --git a/tools/rimage/config/imx8m.toml b/tools/rimage/config/imx8m.toml new file mode 100644 index 000000000000..5b7ba20870bd --- /dev/null +++ b/tools/rimage/config/imx8m.toml @@ -0,0 +1,20 @@ +version = [1, 0] # use simple file write + +[adsp] +name = "imx8m" + +[[adsp.mem_zone]] +type = "IRAM" +base = "0x3b6F8000" +size = "0x800" +host_offset = "0x10000" +[[adsp.mem_zone]] +type = "DRAM" +base = "0x3B6E8000" +size = "0x8000" +host_offset = "0x0" +[[adsp.mem_zone]] +type = "SRAM" +base = "0x92400000" +size = "0x800000" +host_offset = "0x0" diff --git a/tools/rimage/config/imx8ulp.toml b/tools/rimage/config/imx8ulp.toml new file mode 100644 index 000000000000..7fd4c16a52f3 --- /dev/null +++ b/tools/rimage/config/imx8ulp.toml @@ -0,0 +1,20 @@ +version = [1, 0] # use simple file write + +[adsp] +name = "imx8ulp" + +[[adsp.mem_zone]] +type = "IRAM" +base = "0x21170000" +size = "0x10000" +host_offset = "0x0" +[[adsp.mem_zone]] +type = "DRAM" +base = "0x21180000" +size = "0x10000" +host_offset = "0x10000" +[[adsp.mem_zone]] +type = "SRAM" +base = "0x1a000000" +size = "0x800000" +host_offset = "0x0" diff --git a/tools/rimage/config/imx8x.toml b/tools/rimage/config/imx8x.toml new file mode 100644 index 000000000000..ea9059c174ae --- /dev/null +++ b/tools/rimage/config/imx8x.toml @@ -0,0 +1,20 @@ +version = [1, 0] # use simple file write + +[adsp] +name = "imx8x" + +[[adsp.mem_zone]] +type = "IRAM" +base = "0x596F8000" +size = "0x800" +host_offset = "0x10000" +[[adsp.mem_zone]] +type = "DRAM" +base = "0x596e8000" +size = "0x8000" +host_offset = "0x0" +[[adsp.mem_zone]] +type = "SRAM" +base = "0x92400000" +size = "0x800000" +host_offset = "0x0" diff --git a/tools/rimage/config/imx95.toml b/tools/rimage/config/imx95.toml new file mode 100644 index 000000000000..2aa395b53956 --- /dev/null +++ b/tools/rimage/config/imx95.toml @@ -0,0 +1,9 @@ +version = [1, 0] + +[adsp] +name = "imx95" + +[[adsp.mem_zone]] +type = "SRAM" +base = "0x80000000" +size = "0x100000" diff --git a/tools/rimage/config/jsl.toml b/tools/rimage/config/jsl.toml new file mode 100644 index 000000000000..bec0e6bd3a7e --- /dev/null +++ b/tools/rimage/config/jsl.toml @@ -0,0 +1,61 @@ +version = [1, 8] + +[adsp] +name = "icl" +image_size = "0x110000" # (16 + 1) bank * 64KB +alias_mask = "0xE0000000" + +[[adsp.mem_zone]] +type = "ROM" +base = "0xBEFE0000" +size = "0x00002000" +[[adsp.mem_zone]] +type = "IMR" +base = "0xB0038000" +size = "0x100000" +[[adsp.mem_zone]] +type = "SRAM" +base = "0xBE040000" +size = "0x100000" + +[[adsp.mem_alias]] +type = "uncached" +base = "0x9E000000" +[[adsp.mem_alias]] +type = "cached" +base = "0xBE000000" + +[cse] +partition_name = "ADSP" +[[cse.entry]] +name = "ADSP.man" +offset = "0x58" +length = "0x378" +[[cse.entry]] +name = "cavs0015.met" +offset = "0x400" +length = "0x60" +[[cse.entry]] +name = "cavs0015" +offset = "0x480" +length = "0x0" # calculated by rimage + +[css] + +[signed_pkg] +name = "ADSP" +[[signed_pkg.module]] +name = "cavs0015.met" + +[partition_info] +name = "ADSP" +[[partition_info.module]] +name = "cavs0015.met" + +[adsp_file] +[[adsp_file.comp]] +base_offset = "0x2000" + +[fw_desc.header] +name = "ADSPFW" +load_offset = "0x30000" diff --git a/tools/rimage/config/kbl.toml b/tools/rimage/config/kbl.toml new file mode 100644 index 000000000000..f1c83d594905 --- /dev/null +++ b/tools/rimage/config/kbl.toml @@ -0,0 +1,30 @@ +version = [1, 5] + +[adsp] +name = "kbl" +image_size = "0x200000" # (30 + 2) bank * 64KB +alias_mask = "0xE0000000" + +[[adsp.mem_zone]] +type = "ROM" +base = "0xBEFE0000" +size = "0x00002000" +[[adsp.mem_zone]] +type = "SRAM" +base = "0xA000A000" +size = "0x100000" + +[[adsp.mem_alias]] +type = "uncached" +base = "0x9E000000" +[[adsp.mem_alias]] +type = "cached" +base = "0xBE000000" + +[css] + +[fw_desc.header] +name = "ADSPFW" +load_offset = "0" +hw_buf_base_addr = "0xBE500000" +hw_buf_length = "0x4A000" diff --git a/tools/rimage/config/lnl.toml.h b/tools/rimage/config/lnl.toml.h new file mode 100644 index 000000000000..5fc7008bfbe8 --- /dev/null +++ b/tools/rimage/config/lnl.toml.h @@ -0,0 +1,154 @@ +#include "platform-lnl.toml" + + [[module.entry]] + name = "BRNGUP" + uuid = UUIDREG_STR_BRNGUP + affinity_mask = "0x1" + instance_count = "1" + domain_types = "0" + load_type = "0" + module_type = "0" + auto_start = "0" + + index = __COUNTER__ + +#if CONFIG_COLD_STORE_EXECUTE_DRAM + [[module.entry]] + name = "COLD" + uuid = UUIDREG_STR_COLD + affinity_mask = "3" + instance_count = "1" + domain_types = "0" + load_type = "0" + module_type = "0" + auto_start = "0" + + index = __COUNTER__ +#endif + + [[module.entry]] + name = "BASEFW" + uuid = UUIDREG_STR_BASEFW + affinity_mask = "3" + instance_count = "1" + domain_types = "0" + load_type = "0" + module_type = "0" + auto_start = "0" + + index = __COUNTER__ + +#if defined(CONFIG_COMP_TESTER) || defined(LLEXT_FORCE_ALL_MODULAR) +#include +#endif + +#if defined(CONFIG_COMP_MIXIN_MIXOUT) || defined(LLEXT_FORCE_ALL_MODULAR) +#include