From fd3f1a89a78454b3021f8728257571536e54ea96 Mon Sep 17 00:00:00 2001 From: Serhiy Katsyuba Date: Tue, 5 May 2026 11:41:07 +0200 Subject: [PATCH 1/7] audio: move cir_buf_ptr to common header This moves struct cir_buf_ptr from mixin_mixout to a common header so it can be reused by other modules. Signed-off-by: Serhiy Katsyuba --- src/audio/mixin_mixout/mixin_mixout.h | 7 ------- src/include/sof/audio/audio_stream.h | 9 +++++++++ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/audio/mixin_mixout/mixin_mixout.h b/src/audio/mixin_mixout/mixin_mixout.h index 97aa0e12cdab..5cfb30f26c43 100644 --- a/src/audio/mixin_mixout/mixin_mixout.h +++ b/src/audio/mixin_mixout/mixin_mixout.h @@ -99,13 +99,6 @@ struct ipc4_mixer_mode_config { struct ipc4_mixer_mode_sink_config mixer_mode_sink_configs[1]; } __packed __aligned(4); -/* Pointer to data in circular buffer together with buffer boundaries */ -struct cir_buf_ptr { - void *buf_start; - void *buf_end; - void *ptr; -}; - /** * \brief mixin processing function interface */ diff --git a/src/include/sof/audio/audio_stream.h b/src/include/sof/audio/audio_stream.h index dd81c157b8d8..5c6f15392800 100644 --- a/src/include/sof/audio/audio_stream.h +++ b/src/include/sof/audio/audio_stream.h @@ -66,6 +66,15 @@ struct audio_stream { struct sof_audio_stream_params runtime_stream_params; }; +/* A pointer to data in a ring buffer. Just for convenience to reduce the number of typical + * processing function parameters: e.g., just 3 parameters (in, out ptr, and size) instead of 7. + */ +struct cir_buf_ptr { + void *buf_start; + void *buf_end; + void *ptr; +}; + void audio_stream_recalc_align(struct audio_stream *stream); static inline void *audio_stream_get_rptr(const struct audio_stream *buf) From 33a2f8eb5b975bc1d9e3671669c0f1bafd3c2b41 Mon Sep 17 00:00:00 2001 From: Serhiy Katsyuba Date: Fri, 17 Apr 2026 19:44:55 +0200 Subject: [PATCH 2/7] uaol: audio: introduce DSRC This implements a DSRC (Drift-compensating adaptive Sample Rate Converter) for use with the UAOL feedback feature to perform audio resampling and compensate for small dynamic drift. The implementation employs linear interpolation. Signed-off-by: Serhiy Katsyuba --- src/audio/dsrc.c | 92 ++++++++++++++++++++++++++++++++++++ src/include/sof/audio/dsrc.h | 25 ++++++++++ 2 files changed, 117 insertions(+) create mode 100644 src/audio/dsrc.c create mode 100644 src/include/sof/audio/dsrc.h diff --git a/src/audio/dsrc.c b/src/audio/dsrc.c new file mode 100644 index 000000000000..69aa86d8b11e --- /dev/null +++ b/src/audio/dsrc.c @@ -0,0 +1,92 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright 2026 Intel Corporation. All rights reserved. + */ + +#include +#include +#include + +void dsrc_init(struct dsrc *dsrc, size_t channels) +{ + memset(dsrc, 0, sizeof(*dsrc)); + dsrc->channels = channels; +} + +void dsrc_set_rate(struct dsrc *dsrc, uint32_t in_rate, uint32_t out_rate) +{ + if (out_rate > in_rate) { + dsrc->max_phase = ((uint64_t)in_rate << 32) / (out_rate - in_rate); + dsrc->phase_acc = 0; + memset(dsrc->previous_sample_norm, 0, sizeof(dsrc->previous_sample_norm)); + } else { + assert(out_rate == in_rate); + dsrc->max_phase = 0; + } +} + +/* phase_acc and max_phase use uint64_t with << 32 to achieve high fractional precision + * without using floating-point arithmetic. The phase increment is 1 (i.e., 1 << 32). + * Frequency precision is 1 Hz. Audio data is 32-bit. + */ +size_t dsrc_process(struct dsrc *dsrc, const struct cir_buf_ptr *in, + struct cir_buf_ptr *out, size_t frames) +{ + if (dsrc->max_phase == 0) { + /* No resampling needed, just copy the data. */ + cir_buf_copy(in->ptr, in->buf_start, in->buf_end, out->ptr, + out->buf_start, out->buf_end, + sizeof(int32_t) * dsrc->channels * frames); + + return 0; + } + + int32_t *in_ptr = in->ptr; + int32_t *out_ptr = out->ptr; + size_t added_frames = 0; + + /* Two different data types are needed: dsrc->max_phase must be 64 bits to maintain + * precision when calculating dsrc->phase_acc rollover, but max_phase_32 must be 32 bits + * to prevent overflow during multiplication. + */ + uint32_t max_phase_32 = dsrc->max_phase >> 32; + + while (frames--) { + /* Same precision considerations as max_phase_32 above apply here. */ + uint32_t phase_acc_32 = dsrc->phase_acc >> 32; + + for (size_t ch = 0; ch < dsrc->channels; ch++) { + in_ptr = cir_buf_wrap(in_ptr, in->buf_start, in->buf_end); + out_ptr = cir_buf_wrap(out_ptr, out->buf_start, out->buf_end); + + int64_t sample_norm = ((int64_t)*in_ptr << 32) / max_phase_32; + + int64_t previous_sample_norm = dsrc->previous_sample_norm[ch]; + dsrc->previous_sample_norm[ch] = sample_norm; + + *out_ptr = (sample_norm * (max_phase_32 - phase_acc_32) + + previous_sample_norm * phase_acc_32) >> 32; + + in_ptr++; + out_ptr++; + } + + dsrc->phase_acc += (uint64_t)1 << 32; + + if (dsrc->phase_acc >= dsrc->max_phase) { + dsrc->phase_acc -= dsrc->max_phase; + added_frames++; + + /* Insert new frame here */ + for (size_t ch = 0; ch < dsrc->channels; ch++) { + out_ptr = cir_buf_wrap(out_ptr, out->buf_start, out->buf_end); + + *out_ptr = (dsrc->previous_sample_norm[ch] * max_phase_32) >> 32; + + out_ptr++; + } + } + } + + return added_frames; +} diff --git a/src/include/sof/audio/dsrc.h b/src/include/sof/audio/dsrc.h new file mode 100644 index 000000000000..e68563b200fd --- /dev/null +++ b/src/include/sof/audio/dsrc.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright 2026 Intel Corporation. All rights reserved. + */ + +#ifndef __SOF_AUDIO_DSRC_H__ +#define __SOF_AUDIO_DSRC_H__ + +#include +#include +#include + +struct dsrc { + size_t channels; + int64_t previous_sample_norm[PLATFORM_MAX_CHANNELS]; + uint64_t phase_acc; + uint64_t max_phase; +}; + +void dsrc_init(struct dsrc *dsrc, size_t channels); +void dsrc_set_rate(struct dsrc *dsrc, uint32_t in_rate, uint32_t out_rate); +size_t dsrc_process(struct dsrc *dsrc, const struct cir_buf_ptr *in, + struct cir_buf_ptr *out, size_t frames); + +#endif /* __SOF_AUDIO_DSRC_H__ */ From 4201db597bce11d655083326eac9e912199e6717 Mon Sep 17 00:00:00 2001 From: Serhiy Katsyuba Date: Mon, 27 Apr 2026 18:09:14 +0200 Subject: [PATCH 3/7] test: uaol: add DSRC unit tests This adds unit tests for DSRC using Zephyr's ztest framework. Signed-off-by: Serhiy Katsyuba --- test/ztest/unit/dsrc/CMakeLists.txt | 29 + test/ztest/unit/dsrc/prj.conf | 2 + test/ztest/unit/dsrc/test_dsrc_ztest.c | 240 +++++ test/ztest/unit/dsrc/test_expected_2.txt | 1020 ++++++++++++++++++++++ test/ztest/unit/dsrc/test_input.txt | 1000 +++++++++++++++++++++ test/ztest/unit/dsrc/testcase.yaml | 11 + 6 files changed, 2302 insertions(+) create mode 100644 test/ztest/unit/dsrc/CMakeLists.txt create mode 100644 test/ztest/unit/dsrc/prj.conf create mode 100644 test/ztest/unit/dsrc/test_dsrc_ztest.c create mode 100644 test/ztest/unit/dsrc/test_expected_2.txt create mode 100644 test/ztest/unit/dsrc/test_input.txt create mode 100644 test/ztest/unit/dsrc/testcase.yaml diff --git a/test/ztest/unit/dsrc/CMakeLists.txt b/test/ztest/unit/dsrc/CMakeLists.txt new file mode 100644 index 000000000000..a198ace9f231 --- /dev/null +++ b/test/ztest/unit/dsrc/CMakeLists.txt @@ -0,0 +1,29 @@ +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(test_dsrc) + +set(SOF_ROOT "${PROJECT_SOURCE_DIR}/../../../..") + +# Include SOF CMake functions +include(${SOF_ROOT}/scripts/cmake/misc.cmake) + +target_include_directories(app PRIVATE + ${SOF_ROOT}/zephyr/include + ${SOF_ROOT}/src/include + ${SOF_ROOT}/src/platform/posix/include +) + +# Define SOF-specific configurations for unit testing +target_compile_definitions(app PRIVATE + -DCONFIG_ZEPHYR_POSIX=1 + -DTEST_DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}" +) + +target_sources(app PRIVATE + test_dsrc_ztest.c + ${SOF_ROOT}/src/audio/dsrc.c +) + +# Add RELATIVE_FILE definitions for SOF trace functionality +sof_append_relative_path_definitions(app) diff --git a/test/ztest/unit/dsrc/prj.conf b/test/ztest/unit/dsrc/prj.conf new file mode 100644 index 000000000000..d34c7781cd0a --- /dev/null +++ b/test/ztest/unit/dsrc/prj.conf @@ -0,0 +1,2 @@ +CONFIG_ZTEST=y +CONFIG_SOF_FULL_ZEPHYR_APPLICATION=n diff --git a/test/ztest/unit/dsrc/test_dsrc_ztest.c b/test/ztest/unit/dsrc/test_dsrc_ztest.c new file mode 100644 index 000000000000..04af734f50fb --- /dev/null +++ b/test/ztest/unit/dsrc/test_dsrc_ztest.c @@ -0,0 +1,240 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright 2026 Intel Corporation. All rights reserved. + +#include +#include +#include +#include +#include +#include +#include + +/* 200 seconds stereo */ +#define NUM_SAMPLES (48000 * 2 * 200) +static int32_t in_buf[NUM_SAMPLES], out_buf[NUM_SAMPLES]; + +/** + * @brief Read int32_t values from a text file, one number per line. + * + * @param path Path to the input text file. + * @param buf Destination array for the read values. + * @param max Maximum number of elements that fit in @a buf. + * @return Number of values actually read, or negative on error. + */ +static int read_data_from_file(const char *path, int32_t *buf, size_t max) +{ + FILE *f = fopen(path, "r"); + + if (!f) + return -1; + + size_t count = 0; + char line[64]; + + while (count < max && fgets(line, sizeof(line), f)) { + char *end; + long val = strtol(line, &end, 10); + + if (end == line) /* skip blank / non-numeric lines */ + continue; + buf[count++] = (int32_t)val; + } + + fclose(f); + return (int)count; +} + +/** + * @brief Write int32_t values to a text file, one number per line. + * + * @param path Path to the output text file. + * @param buf Source array of values to write. + * @param count Number of elements to write. + * @return 0 on success, negative on error. + */ +static int write_data_to_file(const char *path, const int32_t *buf, size_t count) +{ + FILE *f = fopen(path, "w"); + + if (!f) + return -1; + + for (size_t i = 0; i < count; i++) + fprintf(f, "%d\n", buf[i]); + + fclose(f); + return 0; +} + +/** + * @brief Compare two text files line by line for equality. + * + * @param path_a Path to the first file. + * @param path_b Path to the second file (expected / reference). + * @return 0 when the files are identical, or the 1-based line number + * of the first mismatch. Returns -1 on open/read error. + */ +static int compare_data_files(const char *path_a, const char *path_b) +{ + FILE *fa = fopen(path_a, "r"); + FILE *fb = fopen(path_b, "r"); + + if (!fa || !fb) { + if (fa) + fclose(fa); + if (fb) + fclose(fb); + return -1; + } + + char line_a[64]; + char line_b[64]; + int line = 1; + + while (true) { + char *a = fgets(line_a, sizeof(line_a), fa); + char *b = fgets(line_b, sizeof(line_b), fb); + + if (!a && !b) /* both reached EOF at the same time */ + break; + + if (!a || !b || strcmp(line_a, line_b) != 0) { + fclose(fa); + fclose(fb); + return line; /* first mismatching line (1-based) */ + } + line++; + } + + fclose(fa); + fclose(fb); + return 0; +} + +/* Just to avoid dependency on components.c, copy-paste impl here */ +void cir_buf_copy(void *src, void *src_addr, void *src_end, void *dst, + void *dst_addr, void *dst_end, size_t byte_size) +{ + size_t bytes = byte_size; + size_t bytes_src; + size_t bytes_dst; + size_t bytes_copied; + uint8_t *in = (uint8_t *)src; + uint8_t *out = (uint8_t *)dst; + + while (bytes) { + bytes_src = cir_buf_bytes_without_wrap(in, src_end); + bytes_dst = cir_buf_bytes_without_wrap(out, dst_end); + bytes_copied = MIN(bytes_src, bytes_dst); + bytes_copied = MIN(bytes, bytes_copied); + memcpy_s(out, bytes_copied, in, bytes_copied); + bytes -= bytes_copied; + in = cir_buf_wrap(in + bytes_copied, src_addr, src_end); + out = cir_buf_wrap(out + bytes_copied, dst_addr, dst_end); + } +} + +/**************************** Test cases *******************************/ + +ZTEST(dsrc_suite, test_48000_48000) +{ + struct dsrc dsrc; + dsrc_init(&dsrc, 2); + dsrc_set_rate(&dsrc, 48000, 48000); + + struct cir_buf_ptr in = { in_buf, &in_buf[NUM_SAMPLES], in_buf }; + struct cir_buf_ptr out = { out_buf, &out_buf[NUM_SAMPLES], out_buf }; + size_t added_frames = dsrc_process(&dsrc, &in, &out, 48000 * 100); + + /* same frequencies: zero added frames expected */ + zassert_equal(added_frames, 0); +} + +ZTEST(dsrc_suite, test_48000_48007) +{ + struct dsrc dsrc; + dsrc_init(&dsrc, 2); + dsrc_set_rate(&dsrc, 48000, 48007); + + struct cir_buf_ptr in = { in_buf, &in_buf[NUM_SAMPLES], in_buf }; + struct cir_buf_ptr out = { out_buf, &out_buf[NUM_SAMPLES], out_buf }; + size_t added_frames = dsrc_process(&dsrc, &in, &out, 48000 * 100); + + /* 7 added frames per each input second are expected */ + zassert_equal(added_frames, 7 * 100); +} + +ZTEST(dsrc_suite, test_48000_48007_1ms_chunks) +{ + struct dsrc dsrc; + dsrc_init(&dsrc, 2); + dsrc_set_rate(&dsrc, 48000, 48007); + + size_t frames = 48000 * 100; + size_t total_added_frames = 0; + + struct cir_buf_ptr in = { in_buf, &in_buf[48 * 2], in_buf }; + struct cir_buf_ptr out = { out_buf, &out_buf[48 * 2 + 2], out_buf }; + + while (frames) { + size_t added_frames = dsrc_process(&dsrc, &in, &out, 48); + /* with 1 ms chunks and small freq diff, periodically one extra frame is added */ + zassert_true(added_frames == 0 || added_frames == 1); + total_added_frames += added_frames; + frames -= 48; + } + + /* 7 added frames per each input second are expected */ + zassert_equal(total_added_frames, 7 * 100); +} + +ZTEST(dsrc_suite, test_file_48000_48000) +{ + int n = read_data_from_file(TEST_DATA_DIR "/test_input.txt", in_buf, NUM_SAMPLES); + zassert_true(n > 0, "failed to read test input data"); + + struct dsrc dsrc; + dsrc_init(&dsrc, 2); + dsrc_set_rate(&dsrc, 48000, 48000); + + struct cir_buf_ptr in = { in_buf, &in_buf[NUM_SAMPLES], in_buf }; + struct cir_buf_ptr out = { out_buf, &out_buf[NUM_SAMPLES], out_buf }; + size_t added_frames = dsrc_process(&dsrc, &in, &out, n / 2); + + /* same frequencies: zero added frames expected */ + zassert_equal(added_frames, 0); + + int ret = write_data_to_file(TEST_DATA_DIR "/test_output_1.txt", out_buf, n + added_frames * 2); + zassert_equal(ret, 0, "failed to write test output data"); + + /* no resampling: output should match input */ + int diff = compare_data_files(TEST_DATA_DIR "/test_input.txt", TEST_DATA_DIR "/test_output_1.txt"); + zassert_equal(diff, 0, "test output differs from expected at line %d", diff); +} + +ZTEST(dsrc_suite, test_file_100_102) +{ + int n = read_data_from_file(TEST_DATA_DIR "/test_input.txt", in_buf, NUM_SAMPLES); + zassert_true(n > 0, "failed to read test input data"); + + struct dsrc dsrc; + dsrc_init(&dsrc, 1); + dsrc_set_rate(&dsrc, 100, 102); + + struct cir_buf_ptr in = { in_buf, &in_buf[NUM_SAMPLES], in_buf }; + struct cir_buf_ptr out = { out_buf, &out_buf[NUM_SAMPLES], out_buf }; + size_t added_frames = dsrc_process(&dsrc, &in, &out, n); + + /* 2 added frames per each input second are expected */ + zassert_equal(added_frames, (double)n / 100 * 2); + + int ret = write_data_to_file(TEST_DATA_DIR "/test_output_2.txt", out_buf, n + added_frames); + zassert_equal(ret, 0, "failed to write test output data"); + + /* compare output with expected data */ + int diff = compare_data_files(TEST_DATA_DIR "/test_output_2.txt", TEST_DATA_DIR "/test_expected_2.txt"); + zassert_equal(diff, 0, "test output differs from expected at line %d", diff); +} + +ZTEST_SUITE(dsrc_suite, NULL, NULL, NULL, NULL, NULL); diff --git a/test/ztest/unit/dsrc/test_expected_2.txt b/test/ztest/unit/dsrc/test_expected_2.txt new file mode 100644 index 000000000000..2119b7f7adb8 --- /dev/null +++ b/test/ztest/unit/dsrc/test_expected_2.txt @@ -0,0 +1,1020 @@ +100 +198 +296 +394 +492 +590 +688 +786 +884 +982 +1080 +1178 +1276 +1374 +1472 +1570 +1668 +1766 +1864 +1962 +2060 +2158 +2256 +2354 +2452 +2550 +2648 +2746 +2844 +2942 +3040 +3138 +3236 +3334 +3432 +3530 +3628 +3726 +3824 +3922 +4020 +4118 +4216 +4314 +4412 +4510 +4608 +4706 +4804 +4902 +5000 +5100 +5198 +5296 +5394 +5492 +5590 +5688 +5786 +5884 +5982 +6080 +6178 +6276 +6374 +6472 +6570 +6668 +6766 +6864 +6962 +7060 +7158 +7256 +7354 +7452 +7550 +7648 +7746 +7844 +7942 +8040 +8138 +8236 +8334 +8432 +8530 +8628 +8726 +8824 +8922 +9020 +9118 +9216 +9314 +9412 +9510 +9608 +9706 +9804 +9902 +10000 +10100 +10198 +10296 +10394 +10492 +10590 +10688 +10786 +10884 +10982 +11080 +11178 +11276 +11374 +11472 +11570 +11668 +11766 +11864 +11962 +12060 +12158 +12256 +12354 +12452 +12550 +12648 +12746 +12844 +12942 +13040 +13138 +13236 +13334 +13432 +13530 +13628 +13726 +13824 +13922 +14020 +14118 +14216 +14314 +14412 +14510 +14608 +14706 +14804 +14902 +15000 +15100 +15198 +15296 +15394 +15492 +15590 +15688 +15786 +15884 +15982 +16080 +16178 +16276 +16374 +16472 +16570 +16668 +16766 +16864 +16962 +17060 +17158 +17256 +17354 +17452 +17550 +17648 +17746 +17844 +17942 +18040 +18138 +18236 +18334 +18432 +18530 +18628 +18726 +18824 +18922 +19020 +19118 +19216 +19314 +19412 +19510 +19608 +19706 +19804 +19902 +20000 +20100 +20198 +20296 +20394 +20492 +20590 +20688 +20786 +20884 +20982 +21080 +21178 +21276 +21374 +21472 +21570 +21668 +21766 +21864 +21962 +22060 +22158 +22256 +22354 +22452 +22550 +22648 +22746 +22844 +22942 +23040 +23138 +23236 +23334 +23432 +23530 +23628 +23726 +23824 +23922 +24020 +24118 +24216 +24314 +24412 +24510 +24608 +24706 +24804 +24902 +25000 +25100 +25198 +25296 +25394 +25492 +25590 +25688 +25786 +25884 +25982 +26080 +26178 +26276 +26374 +26472 +26570 +26668 +26766 +26864 +26962 +27060 +27158 +27256 +27354 +27452 +27550 +27648 +27746 +27844 +27942 +28040 +28138 +28236 +28334 +28432 +28530 +28628 +28726 +28824 +28922 +29020 +29118 +29216 +29314 +29412 +29510 +29608 +29706 +29804 +29902 +30000 +30100 +30198 +30296 +30394 +30492 +30590 +30688 +30786 +30884 +30982 +31080 +31178 +31276 +31374 +31472 +31570 +31668 +31766 +31864 +31962 +32060 +32158 +32256 +32354 +32452 +32550 +32648 +32746 +32844 +32942 +33040 +33138 +33236 +33334 +33432 +33530 +33628 +33726 +33824 +33922 +34020 +34118 +34216 +34314 +34412 +34510 +34608 +34706 +34804 +34902 +35000 +35100 +35198 +35296 +35394 +35492 +35590 +35688 +35786 +35884 +35982 +36080 +36178 +36276 +36374 +36472 +36570 +36668 +36766 +36864 +36962 +37060 +37158 +37256 +37354 +37452 +37550 +37648 +37746 +37844 +37942 +38040 +38138 +38236 +38334 +38432 +38530 +38628 +38726 +38824 +38922 +39020 +39118 +39216 +39314 +39412 +39510 +39608 +39706 +39804 +39902 +40000 +40100 +40198 +40296 +40394 +40492 +40590 +40688 +40786 +40884 +40982 +41080 +41178 +41276 +41374 +41472 +41570 +41668 +41766 +41864 +41962 +42060 +42158 +42256 +42354 +42452 +42550 +42648 +42746 +42844 +42942 +43040 +43138 +43236 +43334 +43432 +43530 +43628 +43726 +43824 +43922 +44020 +44118 +44216 +44314 +44412 +44510 +44608 +44706 +44804 +44902 +45000 +45100 +45198 +45296 +45394 +45492 +45590 +45688 +45786 +45884 +45982 +46080 +46178 +46276 +46374 +46472 +46570 +46668 +46766 +46864 +46962 +47060 +47158 +47256 +47354 +47452 +47550 +47648 +47746 +47844 +47942 +48040 +48138 +48236 +48334 +48432 +48530 +48628 +48726 +48824 +48922 +49020 +49118 +49216 +49314 +49412 +49510 +49608 +49706 +49804 +49902 +50000 +50100 +50198 +50296 +50394 +50492 +50590 +50688 +50786 +50884 +50982 +51080 +51178 +51276 +51374 +51472 +51570 +51668 +51766 +51864 +51962 +52060 +52158 +52256 +52354 +52452 +52550 +52648 +52746 +52844 +52942 +53040 +53138 +53236 +53334 +53432 +53530 +53628 +53726 +53824 +53922 +54020 +54118 +54216 +54314 +54412 +54510 +54608 +54706 +54804 +54902 +55000 +55100 +55198 +55296 +55394 +55492 +55590 +55688 +55786 +55884 +55982 +56080 +56178 +56276 +56374 +56472 +56570 +56668 +56766 +56864 +56962 +57060 +57158 +57256 +57354 +57452 +57550 +57648 +57746 +57844 +57942 +58040 +58138 +58236 +58334 +58432 +58530 +58628 +58726 +58824 +58922 +59020 +59118 +59216 +59314 +59412 +59510 +59608 +59706 +59804 +59902 +60000 +60100 +60198 +60296 +60394 +60492 +60590 +60688 +60786 +60884 +60982 +61080 +61178 +61276 +61374 +61472 +61570 +61668 +61766 +61864 +61962 +62060 +62158 +62256 +62354 +62452 +62550 +62648 +62746 +62844 +62942 +63040 +63138 +63236 +63334 +63432 +63530 +63628 +63726 +63824 +63922 +64020 +64118 +64216 +64314 +64412 +64510 +64608 +64706 +64804 +64902 +65000 +65100 +65198 +65296 +65394 +65492 +65590 +65688 +65786 +65884 +65982 +66080 +66178 +66276 +66374 +66472 +66570 +66668 +66766 +66864 +66962 +67060 +67158 +67256 +67354 +67452 +67550 +67648 +67746 +67844 +67942 +68040 +68138 +68236 +68334 +68432 +68530 +68628 +68726 +68824 +68922 +69020 +69118 +69216 +69314 +69412 +69510 +69608 +69706 +69804 +69902 +70000 +70100 +70198 +70296 +70394 +70492 +70590 +70688 +70786 +70884 +70982 +71080 +71178 +71276 +71374 +71472 +71570 +71668 +71766 +71864 +71962 +72060 +72158 +72256 +72354 +72452 +72550 +72648 +72746 +72844 +72942 +73040 +73138 +73236 +73334 +73432 +73530 +73628 +73726 +73824 +73922 +74020 +74118 +74216 +74314 +74412 +74510 +74608 +74706 +74804 +74902 +75000 +75100 +75198 +75296 +75394 +75492 +75590 +75688 +75786 +75884 +75982 +76080 +76178 +76276 +76374 +76472 +76570 +76668 +76766 +76864 +76962 +77060 +77158 +77256 +77354 +77452 +77550 +77648 +77746 +77844 +77942 +78040 +78138 +78236 +78334 +78432 +78530 +78628 +78726 +78824 +78922 +79020 +79118 +79216 +79314 +79412 +79510 +79608 +79706 +79804 +79902 +80000 +80100 +80198 +80296 +80394 +80492 +80590 +80688 +80786 +80884 +80982 +81080 +81178 +81276 +81374 +81472 +81570 +81668 +81766 +81864 +81962 +82060 +82158 +82256 +82354 +82452 +82550 +82648 +82746 +82844 +82942 +83040 +83138 +83236 +83334 +83432 +83530 +83628 +83726 +83824 +83922 +84020 +84118 +84216 +84314 +84412 +84510 +84608 +84706 +84804 +84902 +85000 +85100 +85198 +85296 +85394 +85492 +85590 +85688 +85786 +85884 +85982 +86080 +86178 +86276 +86374 +86472 +86570 +86668 +86766 +86864 +86962 +87060 +87158 +87256 +87354 +87452 +87550 +87648 +87746 +87844 +87942 +88040 +88138 +88236 +88334 +88432 +88530 +88628 +88726 +88824 +88922 +89020 +89118 +89216 +89314 +89412 +89510 +89608 +89706 +89804 +89902 +90000 +90100 +90198 +90296 +90394 +90492 +90590 +90688 +90786 +90884 +90982 +91080 +91178 +91276 +91374 +91472 +91570 +91668 +91766 +91864 +91962 +92060 +92158 +92256 +92354 +92452 +92550 +92648 +92746 +92844 +92942 +93040 +93138 +93236 +93334 +93432 +93530 +93628 +93726 +93824 +93922 +94020 +94118 +94216 +94314 +94412 +94510 +94608 +94706 +94804 +94902 +95000 +95100 +95198 +95296 +95394 +95492 +95590 +95688 +95786 +95884 +95982 +96080 +96178 +96276 +96374 +96472 +96570 +96668 +96766 +96864 +96962 +97060 +97158 +97256 +97354 +97452 +97550 +97648 +97746 +97844 +97942 +98040 +98138 +98236 +98334 +98432 +98530 +98628 +98726 +98824 +98922 +99020 +99118 +99216 +99314 +99412 +99510 +99608 +99706 +99804 +99902 +100000 diff --git a/test/ztest/unit/dsrc/test_input.txt b/test/ztest/unit/dsrc/test_input.txt new file mode 100644 index 000000000000..61b08f4f6e18 --- /dev/null +++ b/test/ztest/unit/dsrc/test_input.txt @@ -0,0 +1,1000 @@ +100 +200 +300 +400 +500 +600 +700 +800 +900 +1000 +1100 +1200 +1300 +1400 +1500 +1600 +1700 +1800 +1900 +2000 +2100 +2200 +2300 +2400 +2500 +2600 +2700 +2800 +2900 +3000 +3100 +3200 +3300 +3400 +3500 +3600 +3700 +3800 +3900 +4000 +4100 +4200 +4300 +4400 +4500 +4600 +4700 +4800 +4900 +5000 +5100 +5200 +5300 +5400 +5500 +5600 +5700 +5800 +5900 +6000 +6100 +6200 +6300 +6400 +6500 +6600 +6700 +6800 +6900 +7000 +7100 +7200 +7300 +7400 +7500 +7600 +7700 +7800 +7900 +8000 +8100 +8200 +8300 +8400 +8500 +8600 +8700 +8800 +8900 +9000 +9100 +9200 +9300 +9400 +9500 +9600 +9700 +9800 +9900 +10000 +10100 +10200 +10300 +10400 +10500 +10600 +10700 +10800 +10900 +11000 +11100 +11200 +11300 +11400 +11500 +11600 +11700 +11800 +11900 +12000 +12100 +12200 +12300 +12400 +12500 +12600 +12700 +12800 +12900 +13000 +13100 +13200 +13300 +13400 +13500 +13600 +13700 +13800 +13900 +14000 +14100 +14200 +14300 +14400 +14500 +14600 +14700 +14800 +14900 +15000 +15100 +15200 +15300 +15400 +15500 +15600 +15700 +15800 +15900 +16000 +16100 +16200 +16300 +16400 +16500 +16600 +16700 +16800 +16900 +17000 +17100 +17200 +17300 +17400 +17500 +17600 +17700 +17800 +17900 +18000 +18100 +18200 +18300 +18400 +18500 +18600 +18700 +18800 +18900 +19000 +19100 +19200 +19300 +19400 +19500 +19600 +19700 +19800 +19900 +20000 +20100 +20200 +20300 +20400 +20500 +20600 +20700 +20800 +20900 +21000 +21100 +21200 +21300 +21400 +21500 +21600 +21700 +21800 +21900 +22000 +22100 +22200 +22300 +22400 +22500 +22600 +22700 +22800 +22900 +23000 +23100 +23200 +23300 +23400 +23500 +23600 +23700 +23800 +23900 +24000 +24100 +24200 +24300 +24400 +24500 +24600 +24700 +24800 +24900 +25000 +25100 +25200 +25300 +25400 +25500 +25600 +25700 +25800 +25900 +26000 +26100 +26200 +26300 +26400 +26500 +26600 +26700 +26800 +26900 +27000 +27100 +27200 +27300 +27400 +27500 +27600 +27700 +27800 +27900 +28000 +28100 +28200 +28300 +28400 +28500 +28600 +28700 +28800 +28900 +29000 +29100 +29200 +29300 +29400 +29500 +29600 +29700 +29800 +29900 +30000 +30100 +30200 +30300 +30400 +30500 +30600 +30700 +30800 +30900 +31000 +31100 +31200 +31300 +31400 +31500 +31600 +31700 +31800 +31900 +32000 +32100 +32200 +32300 +32400 +32500 +32600 +32700 +32800 +32900 +33000 +33100 +33200 +33300 +33400 +33500 +33600 +33700 +33800 +33900 +34000 +34100 +34200 +34300 +34400 +34500 +34600 +34700 +34800 +34900 +35000 +35100 +35200 +35300 +35400 +35500 +35600 +35700 +35800 +35900 +36000 +36100 +36200 +36300 +36400 +36500 +36600 +36700 +36800 +36900 +37000 +37100 +37200 +37300 +37400 +37500 +37600 +37700 +37800 +37900 +38000 +38100 +38200 +38300 +38400 +38500 +38600 +38700 +38800 +38900 +39000 +39100 +39200 +39300 +39400 +39500 +39600 +39700 +39800 +39900 +40000 +40100 +40200 +40300 +40400 +40500 +40600 +40700 +40800 +40900 +41000 +41100 +41200 +41300 +41400 +41500 +41600 +41700 +41800 +41900 +42000 +42100 +42200 +42300 +42400 +42500 +42600 +42700 +42800 +42900 +43000 +43100 +43200 +43300 +43400 +43500 +43600 +43700 +43800 +43900 +44000 +44100 +44200 +44300 +44400 +44500 +44600 +44700 +44800 +44900 +45000 +45100 +45200 +45300 +45400 +45500 +45600 +45700 +45800 +45900 +46000 +46100 +46200 +46300 +46400 +46500 +46600 +46700 +46800 +46900 +47000 +47100 +47200 +47300 +47400 +47500 +47600 +47700 +47800 +47900 +48000 +48100 +48200 +48300 +48400 +48500 +48600 +48700 +48800 +48900 +49000 +49100 +49200 +49300 +49400 +49500 +49600 +49700 +49800 +49900 +50000 +50100 +50200 +50300 +50400 +50500 +50600 +50700 +50800 +50900 +51000 +51100 +51200 +51300 +51400 +51500 +51600 +51700 +51800 +51900 +52000 +52100 +52200 +52300 +52400 +52500 +52600 +52700 +52800 +52900 +53000 +53100 +53200 +53300 +53400 +53500 +53600 +53700 +53800 +53900 +54000 +54100 +54200 +54300 +54400 +54500 +54600 +54700 +54800 +54900 +55000 +55100 +55200 +55300 +55400 +55500 +55600 +55700 +55800 +55900 +56000 +56100 +56200 +56300 +56400 +56500 +56600 +56700 +56800 +56900 +57000 +57100 +57200 +57300 +57400 +57500 +57600 +57700 +57800 +57900 +58000 +58100 +58200 +58300 +58400 +58500 +58600 +58700 +58800 +58900 +59000 +59100 +59200 +59300 +59400 +59500 +59600 +59700 +59800 +59900 +60000 +60100 +60200 +60300 +60400 +60500 +60600 +60700 +60800 +60900 +61000 +61100 +61200 +61300 +61400 +61500 +61600 +61700 +61800 +61900 +62000 +62100 +62200 +62300 +62400 +62500 +62600 +62700 +62800 +62900 +63000 +63100 +63200 +63300 +63400 +63500 +63600 +63700 +63800 +63900 +64000 +64100 +64200 +64300 +64400 +64500 +64600 +64700 +64800 +64900 +65000 +65100 +65200 +65300 +65400 +65500 +65600 +65700 +65800 +65900 +66000 +66100 +66200 +66300 +66400 +66500 +66600 +66700 +66800 +66900 +67000 +67100 +67200 +67300 +67400 +67500 +67600 +67700 +67800 +67900 +68000 +68100 +68200 +68300 +68400 +68500 +68600 +68700 +68800 +68900 +69000 +69100 +69200 +69300 +69400 +69500 +69600 +69700 +69800 +69900 +70000 +70100 +70200 +70300 +70400 +70500 +70600 +70700 +70800 +70900 +71000 +71100 +71200 +71300 +71400 +71500 +71600 +71700 +71800 +71900 +72000 +72100 +72200 +72300 +72400 +72500 +72600 +72700 +72800 +72900 +73000 +73100 +73200 +73300 +73400 +73500 +73600 +73700 +73800 +73900 +74000 +74100 +74200 +74300 +74400 +74500 +74600 +74700 +74800 +74900 +75000 +75100 +75200 +75300 +75400 +75500 +75600 +75700 +75800 +75900 +76000 +76100 +76200 +76300 +76400 +76500 +76600 +76700 +76800 +76900 +77000 +77100 +77200 +77300 +77400 +77500 +77600 +77700 +77800 +77900 +78000 +78100 +78200 +78300 +78400 +78500 +78600 +78700 +78800 +78900 +79000 +79100 +79200 +79300 +79400 +79500 +79600 +79700 +79800 +79900 +80000 +80100 +80200 +80300 +80400 +80500 +80600 +80700 +80800 +80900 +81000 +81100 +81200 +81300 +81400 +81500 +81600 +81700 +81800 +81900 +82000 +82100 +82200 +82300 +82400 +82500 +82600 +82700 +82800 +82900 +83000 +83100 +83200 +83300 +83400 +83500 +83600 +83700 +83800 +83900 +84000 +84100 +84200 +84300 +84400 +84500 +84600 +84700 +84800 +84900 +85000 +85100 +85200 +85300 +85400 +85500 +85600 +85700 +85800 +85900 +86000 +86100 +86200 +86300 +86400 +86500 +86600 +86700 +86800 +86900 +87000 +87100 +87200 +87300 +87400 +87500 +87600 +87700 +87800 +87900 +88000 +88100 +88200 +88300 +88400 +88500 +88600 +88700 +88800 +88900 +89000 +89100 +89200 +89300 +89400 +89500 +89600 +89700 +89800 +89900 +90000 +90100 +90200 +90300 +90400 +90500 +90600 +90700 +90800 +90900 +91000 +91100 +91200 +91300 +91400 +91500 +91600 +91700 +91800 +91900 +92000 +92100 +92200 +92300 +92400 +92500 +92600 +92700 +92800 +92900 +93000 +93100 +93200 +93300 +93400 +93500 +93600 +93700 +93800 +93900 +94000 +94100 +94200 +94300 +94400 +94500 +94600 +94700 +94800 +94900 +95000 +95100 +95200 +95300 +95400 +95500 +95600 +95700 +95800 +95900 +96000 +96100 +96200 +96300 +96400 +96500 +96600 +96700 +96800 +96900 +97000 +97100 +97200 +97300 +97400 +97500 +97600 +97700 +97800 +97900 +98000 +98100 +98200 +98300 +98400 +98500 +98600 +98700 +98800 +98900 +99000 +99100 +99200 +99300 +99400 +99500 +99600 +99700 +99800 +99900 +100000 diff --git a/test/ztest/unit/dsrc/testcase.yaml b/test/ztest/unit/dsrc/testcase.yaml new file mode 100644 index 000000000000..3a5c8bfe6f77 --- /dev/null +++ b/test/ztest/unit/dsrc/testcase.yaml @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: BSD-3-Clause +# +# Copyright 2026 Intel Corporation. All rights reserved. + +tests: + sof.unit.dsrc: + tags: dsrc + platform_allow: native_sim + integration_platforms: + - native_sim + build_only: false From 8a85b86893ed324fbf65208bdb646ba7afe9e1f9 Mon Sep 17 00:00:00 2001 From: Serhiy Katsyuba Date: Wed, 27 May 2026 15:35:27 +0200 Subject: [PATCH 4/7] intel: base_fw: uaol: move UAOL code out of base_fw This moves UAOL-related code to a dedicated uaol.c file, which will be extended with additional UAOL functionality in the future. Signed-off-by: Serhiy Katsyuba --- src/audio/CMakeLists.txt | 3 ++ src/audio/base_fw_intel.c | 79 ++---------------------------------- src/audio/uaol.c | 79 ++++++++++++++++++++++++++++++++++++ src/include/sof/audio/uaol.h | 20 +++++++++ 4 files changed, 106 insertions(+), 75 deletions(-) create mode 100644 src/audio/uaol.c create mode 100644 src/include/sof/audio/uaol.h diff --git a/src/audio/CMakeLists.txt b/src/audio/CMakeLists.txt index 29e602871af7..0c5443078dbf 100644 --- a/src/audio/CMakeLists.txt +++ b/src/audio/CMakeLists.txt @@ -126,6 +126,9 @@ if(NOT CONFIG_COMP_MODULE_SHARED_LIBRARY_BUILD) if(CONFIG_COMP_TONE) add_subdirectory(tone) endif() + if(CONFIG_DAI_INTEL_UAOL) + add_local_sources(sof uaol.c) + endif() if(CONFIG_ZEPHYR_NATIVE_DRIVERS) list(APPEND base_files host-zephyr.c) sof_list_append_ifdef(CONFIG_COMP_DAI base_files dai-zephyr.c) diff --git a/src/audio/base_fw_intel.c b/src/audio/base_fw_intel.c index 67b87a0afed4..57799f285f59 100644 --- a/src/audio/base_fw_intel.c +++ b/src/audio/base_fw_intel.c @@ -24,10 +24,6 @@ #include #include -#if CONFIG_UAOL_INTEL_ADSP -#include -#endif - #include #include #include @@ -37,6 +33,10 @@ #include #endif +#if CONFIG_UAOL_INTEL_ADSP +#include +#endif + struct ipc4_modules_info { uint32_t modules_count; struct sof_man_module modules[0]; @@ -46,22 +46,6 @@ struct ipc4_modules_info { STATIC_ASSERT(sizeof(struct ipc4_modules_info) < SOF_IPC_MSG_MAX_SIZE, invalid_modules_info_struct_size); -#if CONFIG_UAOL_INTEL_ADSP -struct ipc4_uaol_link_capabilities { - uint32_t input_streams_supported : 4; - uint32_t output_streams_supported : 4; - uint32_t bidirectional_streams_supported : 5; - uint32_t rsvd : 19; - uint32_t max_tx_fifo_size; - uint32_t max_rx_fifo_size; -} __packed __aligned(4); - -struct ipc4_uaol_capabilities { - uint32_t link_count; - struct ipc4_uaol_link_capabilities link_caps[]; -} __packed __aligned(4); -#endif /* CONFIG_UAOL_INTEL_ADSP */ - /* * TODO: default to value of ACE1.x platforms. This is defined * in multiple places in Zephyr, mm_drv_intel_adsp.h and @@ -103,61 +87,6 @@ __cold int basefw_vendor_fw_config(uint32_t *data_offset, char *data) return 0; } -#if CONFIG_UAOL_INTEL_ADSP -#define DEV_AND_COMMA(node) DEVICE_DT_GET(node), -static const struct device *uaol_devs[] = { - DT_FOREACH_STATUS_OKAY(intel_adsp_uaol, DEV_AND_COMMA) -}; - -#if !CONFIG_SOF_OS_LINUX_COMPAT_PRIORITY -__cold static void tlv_value_set_uaol_caps(struct sof_tlv *tuple, uint32_t type) -{ - const size_t dev_count = ARRAY_SIZE(uaol_devs); - struct uaol_capabilities dev_cap; - struct ipc4_uaol_capabilities *caps = (struct ipc4_uaol_capabilities *)tuple->value; - size_t caps_size = offsetof(struct ipc4_uaol_capabilities, link_caps[dev_count]); - size_t i; - int ret; - - assert_can_be_cold(); - - memset(caps, 0, caps_size); - - caps->link_count = dev_count; - for (i = 0; i < dev_count; i++) { - ret = uaol_get_capabilities(uaol_devs[i], &dev_cap); - if (ret) - continue; - - caps->link_caps[i].input_streams_supported = dev_cap.input_streams; - caps->link_caps[i].output_streams_supported = dev_cap.output_streams; - caps->link_caps[i].bidirectional_streams_supported = dev_cap.bidirectional_streams; - caps->link_caps[i].max_tx_fifo_size = dev_cap.max_tx_fifo_size; - caps->link_caps[i].max_rx_fifo_size = dev_cap.max_rx_fifo_size; - } - - tlv_value_set(tuple, type, caps_size, caps); -} -#endif /* CONFIG_SOF_OS_LINUX_COMPAT_PRIORITY */ - -__cold static int uaol_stream_id_to_hda_link_stream_id(int uaol_stream_id) -{ - size_t dev_count = ARRAY_SIZE(uaol_devs); - size_t i; - - assert_can_be_cold(); - - for (i = 0; i < dev_count; i++) { - int hda_link_stream_id = uaol_get_mapped_hda_link_stream_id(uaol_devs[i], - uaol_stream_id); - if (hda_link_stream_id >= 0) - return hda_link_stream_id; - } - - return -1; -} -#endif /* CONFIG_UAOL_INTEL_ADSP */ - __cold int basefw_vendor_hw_config(uint32_t *data_offset, char *data) { struct sof_tlv *tuple = (struct sof_tlv *)data; diff --git a/src/audio/uaol.c b/src/audio/uaol.c new file mode 100644 index 000000000000..51e16aaab105 --- /dev/null +++ b/src/audio/uaol.c @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright 2026 Intel Corporation. All rights reserved. + */ + +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(uaol, CONFIG_SOF_LOG_LEVEL); + +struct ipc4_uaol_link_capabilities { + uint32_t input_streams_supported : 4; + uint32_t output_streams_supported : 4; + uint32_t bidirectional_streams_supported : 5; + uint32_t rsvd : 19; + uint32_t max_tx_fifo_size; + uint32_t max_rx_fifo_size; +} __packed __aligned(4); + +struct ipc4_uaol_capabilities { + uint32_t link_count; + struct ipc4_uaol_link_capabilities link_caps[]; +} __packed __aligned(4); + +#define DEV_AND_COMMA(node) DEVICE_DT_GET(node), +static const struct device *uaol_devs[] = { + DT_FOREACH_STATUS_OKAY(intel_adsp_uaol, DEV_AND_COMMA) +}; + +#if !CONFIG_SOF_OS_LINUX_COMPAT_PRIORITY +__cold void tlv_value_set_uaol_caps(struct sof_tlv *tuple, uint32_t type) +{ + const size_t dev_count = ARRAY_SIZE(uaol_devs); + struct uaol_capabilities dev_cap; + struct ipc4_uaol_capabilities *caps = (struct ipc4_uaol_capabilities *)tuple->value; + size_t caps_size = offsetof(struct ipc4_uaol_capabilities, link_caps[dev_count]); + size_t i; + int ret; + + assert_can_be_cold(); + + memset(caps, 0, caps_size); + + caps->link_count = dev_count; + for (i = 0; i < dev_count; i++) { + ret = uaol_get_capabilities(uaol_devs[i], &dev_cap); + if (ret) + continue; + + caps->link_caps[i].input_streams_supported = dev_cap.input_streams; + caps->link_caps[i].output_streams_supported = dev_cap.output_streams; + caps->link_caps[i].bidirectional_streams_supported = dev_cap.bidirectional_streams; + caps->link_caps[i].max_tx_fifo_size = dev_cap.max_tx_fifo_size; + caps->link_caps[i].max_rx_fifo_size = dev_cap.max_rx_fifo_size; + } + + tlv_value_set(tuple, type, caps_size, caps); +} +#endif /* !CONFIG_SOF_OS_LINUX_COMPAT_PRIORITY */ + +__cold int uaol_stream_id_to_hda_link_stream_id(int uaol_stream_id) +{ + size_t dev_count = ARRAY_SIZE(uaol_devs); + size_t i; + + assert_can_be_cold(); + + for (i = 0; i < dev_count; i++) { + int hda_link_stream_id = uaol_get_mapped_hda_link_stream_id(uaol_devs[i], + uaol_stream_id); + if (hda_link_stream_id >= 0) + return hda_link_stream_id; + } + + return -1; +} diff --git a/src/include/sof/audio/uaol.h b/src/include/sof/audio/uaol.h new file mode 100644 index 000000000000..c12e373ea702 --- /dev/null +++ b/src/include/sof/audio/uaol.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright 2026 Intel Corporation. All rights reserved. + */ + +#ifndef __SOF_AUDIO_UAOL_H__ +#define __SOF_AUDIO_UAOL_H__ + +#include +#include + +struct sof_tlv; + +#if !CONFIG_SOF_OS_LINUX_COMPAT_PRIORITY +__cold void tlv_value_set_uaol_caps(struct sof_tlv *tuple, uint32_t type); +#endif /* !CONFIG_SOF_OS_LINUX_COMPAT_PRIORITY */ + +__cold int uaol_stream_id_to_hda_link_stream_id(int uaol_stream_id); + +#endif /* __SOF_AUDIO_UAOL_H__ */ From 1f9185311441812421e7a2f602d673b21f95bb0c Mon Sep 17 00:00:00 2001 From: Serhiy Katsyuba Date: Wed, 27 May 2026 18:43:21 +0200 Subject: [PATCH 5/7] ipc4: uaol: add ipc4_find_dma_config_tlv() This adds yet another function to parse DMA config supplied by the host. Unfortunately, we now have three functions for this purpose. Unlike the existing ipc4_find_dma_config(), ipc4_find_dma_config_tlv() can find multiple DMA configs. For example, a UAOL copier may use two DMA channels: one for the audio USB endpoint and one for the feedback USB endpoint. ipc4_find_dma_config_tlv() can only work when all data in data_buffer is in TLV format; unfortunately, this is not always the case with IPC4 for all gateway types. Therefore, the existing ipc4_find_dma_config() is still needed as it can skip non-TLV blob data at the beginning of data_buffer. The other function, ipc4_find_dma_config_multiple(), works differently: it searches for DMA config for a given ALH stream ID in ALH multi-gateway case. Hence, this third function -- ipc4_find_dma_config_tlv() -- is added to the family :-/. Signed-off-by: Serhiy Katsyuba --- src/audio/copier/copier_dai.c | 2 +- src/include/sof/ipc/topology.h | 1 + src/ipc/ipc4/helper.c | 37 ++++++++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/audio/copier/copier_dai.c b/src/audio/copier/copier_dai.c index dfd2590c7108..3fcc790bc385 100644 --- a/src/audio/copier/copier_dai.c +++ b/src/audio/copier/copier_dai.c @@ -327,7 +327,7 @@ __cold int copier_dai_create(struct comp_dev *dev, struct copier_data *cd, dai.type = SOF_DAI_INTEL_UAOL; dai.is_config_blob = true; cd->gtw_type = ipc4_gtw_alh; - ret = ipc4_find_dma_config(&dai, gtw_cfg_data, gtw_cfg_size); + ret = ipc4_find_dma_config_tlv(&dai, gtw_cfg_data, gtw_cfg_size); if (ret != IPC4_SUCCESS) { comp_err(dev, "No uaol dma_config found in blob!"); return -EINVAL; diff --git a/src/include/sof/ipc/topology.h b/src/include/sof/ipc/topology.h index 3503c7a407d8..ecea8b96eeab 100644 --- a/src/include/sof/ipc/topology.h +++ b/src/include/sof/ipc/topology.h @@ -56,6 +56,7 @@ int ipc4_trigger_chain_dma(struct ipc *ipc, struct ipc4_chain_dma *cdma, bool *d int ipc4_process_on_core(uint32_t core, bool blocking); int ipc4_pipeline_complete(struct ipc *ipc, uint32_t comp_id, uint32_t cmd); int ipc4_find_dma_config(struct ipc_config_dai *dai, uint8_t *data_buffer, uint32_t size); +int ipc4_find_dma_config_tlv(struct ipc_config_dai *dai, uint8_t *data_buffer, size_t size); int ipc4_pipeline_prepare(struct ipc_comp_dev *ppl_icd, uint32_t cmd); int ipc4_pipeline_trigger(struct ipc_comp_dev *ppl_icd, uint32_t cmd, bool *delayed); int ipc4_find_dma_config_multiple(struct ipc_config_dai *dai, uint8_t *data_buffer, diff --git a/src/ipc/ipc4/helper.c b/src/ipc/ipc4/helper.c index 8e3073ab7797..9938d74fb14f 100644 --- a/src/ipc/ipc4/helper.c +++ b/src/ipc/ipc4/helper.c @@ -1300,6 +1300,43 @@ int ipc4_find_dma_config(struct ipc_config_dai *dai, uint8_t *data_buffer, uint3 return IPC4_SUCCESS; } +/* Unlike the above ipc4_find_dma_config(), this can find multiple DMA configs. + * For example, a UAOL copier may use two DMA channels: one for audio and one + * for clock feedback. This function can only work when all data in data_buffer + * is in TLV format; however, this is not always the case for all gateway types. + * Therefore, the above ipc4_find_dma_config() is still used as it can skip non-TLV + * blob data at the beginning of data_buffer. + */ +int ipc4_find_dma_config_tlv(struct ipc_config_dai *dai, uint8_t *data_buffer, size_t size) +{ + uint8_t *end_addr = data_buffer + size; + struct sof_tlv *tlvs; + struct ipc_dma_config *dma_cfg; + int count = 0; + + for (tlvs = (struct sof_tlv *)data_buffer; (uint8_t *)tlvs < end_addr; + tlvs = tlv_next(tlvs)) { + dma_cfg = tlv_value_ptr_get(tlvs, GTW_DMA_CONFIG_ID); + if (!dma_cfg) + continue; + + if (count >= GTW_DMA_DEVICE_MAX_COUNT) { + tr_err(&ipc_tr, "Unexpected DMA config count %d, max %d", + count, GTW_DMA_DEVICE_MAX_COUNT); + return IPC4_INVALID_REQUEST; + } + + dai->host_dma_config[count++] = dma_cfg; + } + + if (count == 0) { + tr_err(&ipc_tr, "No DMA config found"); + return IPC4_INVALID_REQUEST; + } + + return IPC4_SUCCESS; +} + int ipc4_find_dma_config_multiple(struct ipc_config_dai *dai, uint8_t *data_buffer, uint32_t size, uint32_t device_id, int dma_cfg_idx) { From 5474231e9d31008b59e092e3563d3e83162707dc Mon Sep 17 00:00:00 2001 From: Serhiy Katsyuba Date: Tue, 10 Mar 2026 11:22:50 +0100 Subject: [PATCH 6/7] uaol: add UAOL feedback support This extends UAOL functionality to support audio rate adjustment based on the "desired" clock reported by the USB playback device via the USB feedback endpoint. The firmware calculates drift from this feedback and instructs UAOL hardware to increase or decrease the transfer rate accordingly. A DSRC (Drift-compensating adaptive Sample Rate Converter) is used when necessary to perform audio resampling and compensate for small dynamic drift. Signed-off-by: Serhiy Katsyuba --- src/audio/CMakeLists.txt | 2 +- src/audio/dai-zephyr.c | 167 ++++++++++++++++-- src/audio/uaol.c | 287 +++++++++++++++++++++++++++++++ src/include/sof/audio/uaol.h | 21 +++ src/include/sof/lib/dai-zephyr.h | 25 +++ src/ipc/ipc4/dai.c | 14 ++ 6 files changed, 497 insertions(+), 19 deletions(-) diff --git a/src/audio/CMakeLists.txt b/src/audio/CMakeLists.txt index 0c5443078dbf..a67efe0d5118 100644 --- a/src/audio/CMakeLists.txt +++ b/src/audio/CMakeLists.txt @@ -127,7 +127,7 @@ if(NOT CONFIG_COMP_MODULE_SHARED_LIBRARY_BUILD) add_subdirectory(tone) endif() if(CONFIG_DAI_INTEL_UAOL) - add_local_sources(sof uaol.c) + add_local_sources(sof uaol.c dsrc.c) endif() if(CONFIG_ZEPHYR_NATIVE_DRIVERS) list(APPEND base_files host-zephyr.c) diff --git a/src/audio/dai-zephyr.c b/src/audio/dai-zephyr.c index e49d3e0f0122..5fe3afde41c8 100644 --- a/src/audio/dai-zephyr.c +++ b/src/audio/dai-zephyr.c @@ -46,6 +46,10 @@ #include #include +#ifdef CONFIG_DAI_INTEL_UAOL +#include +#include +#endif #include @@ -352,8 +356,14 @@ dai_dma_cb(struct dai_data *dd, struct comp_dev *dev, uint32_t bytes, } } #endif - ret = dma_buffer_copy_to(dd->local_buffer, dd->dma_buffer, - dd->process, bytes, dd->chmap); + +#ifdef CONFIG_DAI_INTEL_UAOL + if (dd->uaol.feedback_drift) + ret = uaol_dma_buffer_copy_to(dd, bytes); + else +#endif /* CONFIG_DAI_INTEL_UAOL */ + ret = dma_buffer_copy_to(dd->local_buffer, dd->dma_buffer, + dd->process, bytes, dd->chmap); } else { audio_stream_invalidate(&dd->dma_buffer->stream, bytes); /* @@ -435,6 +445,12 @@ dai_dma_cb(struct dai_data *dd, struct comp_dev *dev, uint32_t bytes, /* update host position (in bytes offset) for drivers */ dd->total_data_processed += bytes; } + +#ifdef CONFIG_DAI_INTEL_UAOL + if (dd->uaol.fb_chan_idx >= 0) + process_uaol_feedback(dev, dd); +#endif /* CONFIG_DAI_INTEL_UAOL */ + #ifdef CONFIG_SOF_TELEMETRY_IO_PERFORMANCE_MEASUREMENTS /* Increment performance counters */ io_perf_monitor_update_data(dd->io_perf_dai_byte_count, bytes); @@ -593,6 +609,75 @@ __cold int dai_common_new(struct dai_data *dd, struct comp_dev *dev, return 0; } +static void dai_dma_release_channel(struct dai_data *dd) +{ + if (dd->chan_index >= 0) { + sof_dma_release_channel(dd->dma, dd->chan_index); + dd->chan_index = -EINVAL; + } + +#if CONFIG_UAOL_INTEL_ADSP + if (dd->uaol.fb_chan_idx >= 0) { + sof_dma_release_channel(dd->dma, dd->uaol.fb_chan_idx); + dd->uaol.fb_chan_idx = -EINVAL; + } +#endif +} + +static int dai_dma_config(struct dai_data *dd) +{ + int ret = sof_dma_config(dd->dma, dd->chan_index, dd->z_config); + if (ret < 0) + return ret; + +#if CONFIG_UAOL_INTEL_ADSP + if (dd->uaol.fb_chan_idx >= 0) + ret = sof_dma_config(dd->dma, dd->uaol.fb_chan_idx, dd->uaol.fb_z_config); +#endif + + return ret; +} + +static int dai_dma_start(struct dai_data *dd) +{ + int ret = sof_dma_start(dd->dma, dd->chan_index); + if (ret < 0) + return ret; + +#if CONFIG_UAOL_INTEL_ADSP + if (dd->uaol.fb_chan_idx >= 0) + ret = sof_dma_start(dd->dma, dd->uaol.fb_chan_idx); +#endif + + return ret; +} + +static int dai_dma_stop(struct dai_data *dd) +{ + int ret = sof_dma_stop(dd->dma, dd->chan_index); + +#if CONFIG_UAOL_INTEL_ADSP + /* seems it's better to stop feedback even when the above fails */ + if (dd->uaol.fb_chan_idx >= 0) + sof_dma_stop(dd->dma, dd->uaol.fb_chan_idx); +#endif + + return ret; +} + +static int dai_dma_suspend(struct dai_data *dd) +{ + int ret = sof_dma_suspend(dd->dma, dd->chan_index); + +#if CONFIG_UAOL_INTEL_ADSP + /* seems it's better to suspend feedback even when the above fails */ + if (dd->uaol.fb_chan_idx >= 0) + sof_dma_suspend(dd->dma, dd->uaol.fb_chan_idx); +#endif + + return ret; +} + __cold static struct comp_dev *dai_new(const struct comp_driver *drv, const struct comp_ipc_config *config, const void *spec) @@ -649,11 +734,7 @@ __cold void dai_common_free(struct dai_data *dd) if (dd->group) dai_group_put(dd->group); - if (dd->chan_index >= 0) { - sof_dma_release_channel(dd->dma, dd->chan_index); - dd->chan_index = -EINVAL; - } - + dai_dma_release_channel(dd); sof_dma_put(dd->dma); dai_release_llp_slot(dd); @@ -661,6 +742,10 @@ __cold void dai_common_free(struct dai_data *dd) dai_put(dd->dai); sof_heap_free(dd->alloc_ctx.heap, dd->dai_spec_config); + +#if CONFIG_UAOL_INTEL_ADSP + uaol_free(dd); +#endif } __cold static void dai_free(struct comp_dev *dev) @@ -1143,8 +1228,33 @@ int dai_common_params(struct dai_data *dd, struct comp_dev *dev, } err = dai_set_dma_config(dd, dev); - if (err < 0) + if (err < 0) { comp_err(dev, "set dma config failed."); + goto out; + } + + /* Ideally, this should be moved into setup_uaol_feedback_dma() in uaol.c, but + * there is no easy access to "params" there to set up the buffer format. + */ +#ifdef CONFIG_DAI_INTEL_UAOL + /* create DSRC output buffer (if needed) */ + if (dd->ipc_config.type == SOF_DAI_INTEL_UAOL && + dd->ipc_config.direction == SOF_IPC_STREAM_PLAYBACK) { + /* resampling might generate 1 extra frame; DSRC only works with 32-bit data */ + size_t dsrc_buf_size = (dev->frames + 1) * dd->ipc_config.gtw_fmt->channels_count * 4; + dd->uaol.dsrc_buf = buffer_alloc_range(NULL, dsrc_buf_size, dsrc_buf_size, + SOF_MEM_FLAG_USER, PLATFORM_DCACHE_ALIGN, + BUFFER_USAGE_NOT_SHARED); + if (!dd->uaol.dsrc_buf) { + comp_err(dev, "failed to alloc dsrc buffer"); + goto out; + } + + /* params should be same as local_buffer's */ + buffer_set_params(dd->uaol.dsrc_buf, ¶ms, BUFFER_UPDATE_FORCE); + } +#endif /* CONFIG_DAI_INTEL_UAOL */ + out: /* * Make sure to free all allocated items, all functions @@ -1210,6 +1320,11 @@ int dai_common_config_prepare(struct dai_data *dd, struct comp_dev *dev) comp_dbg(dev, "new configured dma channel index %d", dd->chan_index); +#ifdef CONFIG_DAI_INTEL_UAOL + /* Does nothing if feedback DMA is not needed */ + setup_uaol_feedback_dma(dd, dev); +#endif /* CONFIG_DAI_INTEL_UAOL */ + return 0; } @@ -1233,6 +1348,10 @@ int dai_common_prepare(struct dai_data *dd, struct comp_dev *dev) /* clear dma buffer to avoid pop noise */ buffer_zero(dd->dma_buffer); +#ifdef CONFIG_DAI_INTEL_UAOL + if (dd->uaol.fb_dma_buf) + memset(dd->uaol.fb_dma_buf, 0, dd->uaol.fb_dma_buf_size); +#endif /* CONFIG_DAI_INTEL_UAOL */ /* dma reconfig not required if XRUN handling */ if (dd->xrun) { @@ -1241,7 +1360,7 @@ int dai_common_prepare(struct dai_data *dd, struct comp_dev *dev) return 0; } - ret = sof_dma_config(dd->dma, dd->chan_index, dd->z_config); + ret = dai_dma_config(dd); if (ret < 0) comp_set_state(dev, COMP_TRIGGER_RESET); @@ -1292,6 +1411,10 @@ void dai_common_reset(struct dai_data *dd, struct comp_dev *dev) dd->dma_buffer = NULL; } +#ifdef CONFIG_DAI_INTEL_UAOL + uaol_free(dd); +#endif /* CONFIG_DAI_INTEL_UAOL */ + dd->wallclock = 0; dd->total_data_processed = 0; dd->xrun = 0; @@ -1328,7 +1451,7 @@ static int dai_comp_trigger_internal(struct dai_data *dd, struct comp_dev *dev, /* only start the DAI if we are not XRUN handling */ if (dd->xrun == 0) { - ret = sof_dma_start(dd->dma, dd->chan_index); + ret = dai_dma_start(dd); if (ret < 0) return ret; @@ -1349,6 +1472,14 @@ static int dai_comp_trigger_internal(struct dai_data *dd, struct comp_dev *dev, buffer_zero(dd->dma_buffer); } +#ifdef CONFIG_DAI_INTEL_UAOL + /* It might be beneficial to clear any old obsolete feedback value to prevent + * it from being used to adjust the rate immediately after resume. A feedback + * value of 0 will be rejected by the sanity check. */ + if (dd->uaol.fb_dma_buf) + memset(dd->uaol.fb_dma_buf, 0, dd->uaol.fb_dma_buf_size); +#endif /* CONFIG_DAI_INTEL_UAOL */ + /* DMA driver and SOF's view of the DMA buffer's * read and write cursors must be the same to * avoid scenarios in which the DMA driver @@ -1366,16 +1497,16 @@ static int dai_comp_trigger_internal(struct dai_data *dd, struct comp_dev *dev, /* only start the DAI if we are not XRUN handling */ if (dd->xrun == 0) { /* recover valid start position */ - ret = sof_dma_stop(dd->dma, dd->chan_index); + ret = dai_dma_stop(dd); if (ret < 0) return ret; /* dma_config needed after stop */ - ret = sof_dma_config(dd->dma, dd->chan_index, dd->z_config); + ret = dai_dma_config(dd); if (ret < 0) return ret; - ret = sof_dma_start(dd->dma, dd->chan_index); + ret = dai_dma_start(dd); if (ret < 0) return ret; @@ -1403,11 +1534,11 @@ static int dai_comp_trigger_internal(struct dai_data *dd, struct comp_dev *dev, * as soon as possible. */ #if CONFIG_COMP_DAI_STOP_TRIGGER_ORDER_REVERSE - ret = sof_dma_stop(dd->dma, dd->chan_index); + ret = dai_dma_stop(dd); dai_trigger_op(dd->dai, cmd, dev->direction); #else dai_trigger_op(dd->dai, cmd, dev->direction); - ret = sof_dma_stop(dd->dma, dd->chan_index); + ret = dai_dma_stop(dd); if (ret) { comp_warn(dev, "dma was stopped earlier"); ret = 0; @@ -1417,11 +1548,11 @@ static int dai_comp_trigger_internal(struct dai_data *dd, struct comp_dev *dev, case COMP_TRIGGER_PAUSE: comp_dbg(dev, "PAUSE"); #if CONFIG_COMP_DAI_STOP_TRIGGER_ORDER_REVERSE - ret = sof_dma_suspend(dd->dma, dd->chan_index); + ret = dai_dma_suspend(dd); dai_trigger_op(dd->dai, cmd, dev->direction); #else dai_trigger_op(dd->dai, cmd, dev->direction); - ret = sof_dma_suspend(dd->dma, dd->chan_index); + ret = dai_dma_suspend(dd); #endif break; case COMP_TRIGGER_PRE_START: @@ -1852,7 +1983,7 @@ int dai_common_copy(struct dai_data *dd, struct comp_dev *dev, pcm_converter_fun comp_warn(dev, "dai trigger copy failed"); if (dai_dma_cb(dd, dev, copy_bytes, converter) == SOF_DMA_CB_STATUS_END) - sof_dma_stop(dd->dma, dd->chan_index); + dai_dma_stop(dd); ret = sof_dma_reload(dd->dma, dd->chan_index, copy_bytes); if (ret < 0) { diff --git a/src/audio/uaol.c b/src/audio/uaol.c index 51e16aaab105..67b945ac36c6 100644 --- a/src/audio/uaol.c +++ b/src/audio/uaol.c @@ -7,6 +7,7 @@ #include #include #include +#include #include LOG_MODULE_REGISTER(uaol, CONFIG_SOF_LOG_LEVEL); @@ -77,3 +78,289 @@ __cold int uaol_stream_id_to_hda_link_stream_id(int uaol_stream_id) return -1; } + +const struct device *get_uaol_zdevice(int uaol_link_id) +{ + /* uaol_link_id is just an index for the device tree device */ + assert(uaol_link_id < ARRAY_SIZE(uaol_devs)); + return uaol_devs[uaol_link_id]; +} + +/* These are called from dai-zephyr */ + +/* The UAOL DAI device has a DMA stream with a corresponding mapped (hardwired in HW) + * UAOL device stream. These streams are separate entities. This function returns + * the UAOL device stream ID that is mapped to the UAOL DAI DMA stream. + */ +int dai_get_uaol_stream_id(struct dai *dai, int *uaol_link_id, int *uaol_stream_id) +{ + const struct dai_properties *props; + k_spinlock_key_t key; + + key = k_spin_lock(&dai->lock); + + props = dai_get_properties(dai->dev, 0, 0); + *uaol_link_id = props->uaol_link_id; + *uaol_stream_id = props->uaol_stream_id; + + k_spin_unlock(&dai->lock, key); + + return 0; +} + +/* Reads the feedback frequency (USB playback device "desired" frequency) from the feedback + * USB endpoint via the feedback DMA channel. Then updates the DSRC rate if necessary. Called + * from the dai-zephyr DMA callback. + */ +void process_uaol_feedback(struct comp_dev *dev, struct dai_data *dd) +{ + assert(dd && dd->uaol.fb_chan_idx >= 0 && dd->uaol.fb_dma_buf); + + struct dma_status stat = {0}; + int ret = sof_dma_get_status(dd->dma, dd->uaol.fb_chan_idx, &stat); + if (ret) { + comp_err(dev, "Failed to get UAOL feedback DMA status: %d", ret); + return; + } + + /* Expected feedback length is 4 bytes */ + if (stat.pending_length < 4) { + /* No feedback is normal, as it is sent rarely (e.g., every 128 ms or less often). */ + /* comp_dbg(dev, "No feedback data: %d bytes", stat.pending_length); */ + return; + } + + assert(is_uncached(dd->uaol.fb_dma_buf)); /* Use uncached pointer to read DMA buffer */ + assert(dd->uaol.fb_dma_buf_size >= 4); + + /* NOTE: Not all DMA drivers populate stat.write_position. Intel ACE HDA does. */ + assert((stat.write_position & 3) == 0); /* 4-byte alignment check. */ + /* Read the last received 4 bytes (ignore older ones if any). */ + int last_4_bytes_pos = stat.write_position >= 4 ? (stat.write_position - 4) : + (dd->uaol.fb_dma_buf_size - 4); + uint32_t feedback_value = dd->uaol.fb_dma_buf[last_4_bytes_pos / 4]; + + ret = sof_dma_reload(dd->dma, dd->uaol.fb_chan_idx, stat.pending_length); + if (ret < 0) { + comp_err(dev, "Failed to reload UAOL feedback DMA: %d, pending_length: %d", + ret, stat.pending_length); + return; + } + + comp_dbg(dev, "UAOL feedback: 0x%x", feedback_value); + + const struct device *uaol_zdev = get_uaol_zdevice(dd->uaol.link_id); + int freq = uaol_interpret_feedback_value(uaol_zdev, dd->uaol.stream_id, feedback_value); + + if (freq < 0) { + comp_err(dev, "Unexpected feedback value: 0x%x, err/freq: %d", feedback_value, freq); + return; + } + + /* Let's do a sanity check first to see if the received value looks valid. + * Then limit the maximum drift to a reasonable value to prevent significant + * audio distortion when, for some reason, the reported drift is quite large. + */ + #define REJECT_DRIFT_HZ 1000 + #define CLIP_DRIFT_HZ 6 + + int drift = freq - dd->ipc_config.sampling_frequency; + + if (drift < -REJECT_DRIFT_HZ || drift > REJECT_DRIFT_HZ) { + comp_err(dev, "Weird UAOL feedback freq value: %d, drift: %d. Rejected!", + freq, drift); + return; + } + if (drift < -CLIP_DRIFT_HZ || drift > CLIP_DRIFT_HZ) { + comp_warn(dev, "Unreasonable UAOL feedback freq value: %d, drift: %d. Clipped!", + freq, drift); + drift = MAX(-CLIP_DRIFT_HZ, MIN(drift, CLIP_DRIFT_HZ)); + } + + dd->uaol.feedback_drift = drift; + if (dd->uaol.feedback_drift > 0) + dsrc_set_rate(&dd->uaol.dsrc, dd->ipc_config.sampling_frequency, + drift + dd->ipc_config.sampling_frequency); +} + +/* Tells UAOL HW to skip one audio frame or copy one additional audio frame + * on the next service interval to adjust the data rate. + */ +void adjust_uaol_rate(const struct dai_data *dd, bool increase) +{ + const struct device *uaol_zdev = get_uaol_zdevice(dd->uaol.link_id); + int ret = uaol_adjust_rate(uaol_zdev, dd->uaol.stream_id, increase); + + if (ret != 0) + comp_err(dd->dai_dev, "Failed to adjust UAOL rate: %d", ret); +} + +/* For UAOL playback with non-zero feedback frequency drift, the UAOL rate should be adjusted + * periodically to compensate for the drift. Additionally, for positive drift, DSRC is used + * to resample audio and insert additional audio frame as needed. + * This function is called from the dai-zephyr dai_dma_cb(). + */ +int uaol_dma_buffer_copy_to(struct dai_data *dd, size_t bytes) +{ + int ret = 0; + + assert(dd->uaol.feedback_drift != 0); + + if (dd->uaol.feedback_drift > 0) { + buffer_stream_invalidate(dd->local_buffer, bytes); + + struct cir_buf_ptr in = { dd->local_buffer->stream.addr, + dd->local_buffer->stream.end_addr, dd->local_buffer->stream.r_ptr }; + assert(dd->uaol.dsrc_buf); + struct cir_buf_ptr out = { dd->uaol.dsrc_buf->stream.addr, + dd->uaol.dsrc_buf->stream.end_addr, dd->uaol.dsrc_buf->stream.w_ptr }; + size_t frames = bytes / audio_stream_frame_bytes(&dd->local_buffer->stream); + + size_t added_frames = dsrc_process(&dd->uaol.dsrc, &in, &out, frames); + size_t src_added_bytes = added_frames * + audio_stream_frame_bytes(&dd->local_buffer->stream); + + comp_update_buffer_consume(dd->local_buffer, bytes); + audio_stream_produce(&dd->uaol.dsrc_buf->stream, bytes + src_added_bytes); + + if (added_frames) + adjust_uaol_rate(dd, true); + + /* dma_buffer_copy_to() may do format conversion so + * dst_added_bytes may not be equal to src_added_bytes. + */ + size_t dst_added_bytes = added_frames * audio_stream_frame_bytes(&dd->dma_buffer->stream); + ret = dma_buffer_copy_to(dd->uaol.dsrc_buf, dd->dma_buffer, + dd->process, bytes + dst_added_bytes, dd->chmap); + } else if (dd->uaol.feedback_drift < 0) { + /* It is expected this func is called every 1 ms */ + dd->uaol.ms_since_last_adjustment++; + assert(dd->uaol.feedback_drift < 0 && dd->uaol.feedback_drift >= -1000); + if (-1000 / dd->uaol.feedback_drift >= dd->uaol.ms_since_last_adjustment) { + dd->uaol.ms_since_last_adjustment = 0; + adjust_uaol_rate(dd, false); + } + + ret = dma_buffer_copy_to(dd->local_buffer, dd->dma_buffer, + dd->process, bytes, dd->chmap); + } else + return -EINVAL; + + return ret; +} + +/* When the host setups two DMA links for UAOL playback gateway, that tells us the second + * DMA link is to read from the USB feedback endpoint. This function (re)setups the feedback + * DMA channel and buffer for such case. + */ +int setup_uaol_feedback_dma(struct dai_data *dd, struct comp_dev *dev) +{ + struct ipc_config_dai *dai = &dd->ipc_config; + struct dma_config *dma_cfg; + + dd->uaol.fb_chan_idx = -EINVAL; + dd->uaol.feedback_drift = 0; + dd->uaol.ms_since_last_adjustment = 0; + + if (dai->type != SOF_DAI_INTEL_UAOL || dai->direction != SOF_IPC_STREAM_PLAYBACK) + return 0; + + /* UAOL feedback is an optional 2nd DMA link (1st is audio DMA link) */ + assert(GTW_DMA_DEVICE_MAX_COUNT >= 2); + if (!dai->host_dma_config[1] || !dai->host_dma_config[1]->pre_allocated_by_host) { + comp_info(dev, "No UAOL feedback DMA link supplied by host."); + return 0; + } + + int channel = dai->host_dma_config[1]->dma_channel_id; + comp_dbg(dev, "UAOL feedback channel: %d", channel); + + /* USB feedback endpoint payload is 4 bytes. Hi-Speed USB microframe + * period is 125 us (8 per 1 ms). Double the buffer size just in case. + */ + dd->uaol.fb_dma_buf_size = 4 * 8 * 2; + + /* TODO: perhaps get alignment by reading DMA alignment attribute? */ + dd->uaol.fb_dma_buf = (uint32_t *)rballoc_align(SOF_MEM_FLAG_USER | SOF_MEM_FLAG_DMA, + dd->uaol.fb_dma_buf_size, 64); + if (!dd->uaol.fb_dma_buf) { + comp_err(dev, "UAOL feedback buffer allocation failed!"); + return -ENOMEM; + } + memset(dd->uaol.fb_dma_buf, 0, dd->uaol.fb_dma_buf_size); + + dma_cfg = rballoc(SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT | SOF_MEM_FLAG_DMA, + sizeof(struct dma_config)); + if (!dma_cfg) { + rfree(dd->uaol.fb_dma_buf); + dd->uaol.fb_dma_buf = NULL; + comp_err(dev, "dma_cfg allocation failed"); + return -ENOMEM; + } + + memset(dma_cfg, 0, sizeof(struct dma_config)); + dma_cfg->dma_slot = 0; + dma_cfg->channel_direction = PERIPHERAL_TO_MEMORY; + dma_cfg->source_data_size = 4; + dma_cfg->dest_data_size = 4; + dma_cfg->source_burst_length = 4; + dma_cfg->dest_burst_length = 4; + dma_cfg->cyclic = 1; + dma_cfg->block_count = 1; + + dma_cfg->head_block = rballoc(SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT | SOF_MEM_FLAG_DMA, + sizeof(struct dma_block_config)); + if (!dma_cfg->head_block) { + rfree(dma_cfg); + rfree(dd->uaol.fb_dma_buf); + dd->uaol.fb_dma_buf = NULL; + comp_err(dev, "dma_block_config allocation failed"); + return -ENOMEM; + } + + memset(dma_cfg->head_block, 0, sizeof(struct dma_block_config)); + dma_cfg->head_block->dest_scatter_en = 0; + dma_cfg->head_block->block_size = dd->uaol.fb_dma_buf_size; + dma_cfg->head_block->source_addr_adj = DMA_ADDR_ADJ_INCREMENT; + dma_cfg->head_block->dest_addr_adj = DMA_ADDR_ADJ_DECREMENT; /* WHY? IS THIS OK??? */ + dma_cfg->head_block->source_address = 0; + dma_cfg->head_block->dest_address = (uint32_t)dd->uaol.fb_dma_buf; + dma_cfg->head_block->next_block = dma_cfg->head_block; + dd->uaol.fb_z_config = dma_cfg; + + dd->uaol.fb_chan_idx = sof_dma_request_channel(dd->dma, channel); + if (dd->uaol.fb_chan_idx < 0) { + rfree(dma_cfg->head_block); + rfree(dma_cfg); + rfree(dd->uaol.fb_dma_buf); + dd->uaol.fb_dma_buf = NULL; + comp_err(dev, "dma_request_channel() failed"); + return -EIO; + } + + dsrc_init(&dd->uaol.dsrc, dai->gtw_fmt->channels_count); + + comp_dbg(dev, "New configured UAOL feedback DMA channel: %d", dd->uaol.fb_chan_idx); + + return 0; +} + +void uaol_free(struct dai_data *dd) +{ + if (dd->uaol.fb_z_config) { + rfree(dd->uaol.fb_z_config->head_block); + rfree(dd->uaol.fb_z_config); + dd->uaol.fb_z_config = NULL; + } + + if (dd->uaol.fb_dma_buf) { + rfree(dd->uaol.fb_dma_buf); + dd->uaol.fb_dma_buf = NULL; + dd->uaol.fb_dma_buf_size = 0; + } + + if (dd->uaol.dsrc_buf) { + buffer_free(dd->uaol.dsrc_buf); + dd->uaol.dsrc_buf = NULL; + } +} diff --git a/src/include/sof/audio/uaol.h b/src/include/sof/audio/uaol.h index c12e373ea702..f050b7aba254 100644 --- a/src/include/sof/audio/uaol.h +++ b/src/include/sof/audio/uaol.h @@ -10,6 +10,7 @@ #include struct sof_tlv; +struct device; #if !CONFIG_SOF_OS_LINUX_COMPAT_PRIORITY __cold void tlv_value_set_uaol_caps(struct sof_tlv *tuple, uint32_t type); @@ -17,4 +18,24 @@ __cold void tlv_value_set_uaol_caps(struct sof_tlv *tuple, uint32_t type); __cold int uaol_stream_id_to_hda_link_stream_id(int uaol_stream_id); +const struct device *get_uaol_zdevice(int uaol_link_id); + +/* these are called from dai-zephyr.c */ + +struct dai; +struct comp_dev; +struct dai_data; + +int dai_get_uaol_stream_id(struct dai *dai, int *uaol_link_id, int *uaol_stream_id); + +void process_uaol_feedback(struct comp_dev *dev, struct dai_data *dd); + +void adjust_uaol_rate(const struct dai_data *dd, bool increase); + +int uaol_dma_buffer_copy_to(struct dai_data *dd, size_t bytes); + +int setup_uaol_feedback_dma(struct dai_data *dd, struct comp_dev *dev); + +void uaol_free(struct dai_data *dd); + #endif /* __SOF_AUDIO_UAOL_H__ */ diff --git a/src/include/sof/lib/dai-zephyr.h b/src/include/sof/lib/dai-zephyr.h index 595d11de9b47..0f1d12aba212 100644 --- a/src/include/sof/lib/dai-zephyr.h +++ b/src/include/sof/lib/dai-zephyr.h @@ -30,6 +30,9 @@ #include #include #include +#ifdef CONFIG_DAI_INTEL_UAOL +#include +#endif #include #include #include @@ -114,6 +117,24 @@ typedef int (*channel_copy_func)(const struct audio_stream *src, unsigned int sr struct audio_stream *dst, unsigned int dst_channel, unsigned int frames); +#ifdef CONFIG_DAI_INTEL_UAOL +struct uaol_dai_data { + int feedback_drift; + uint32_t ms_since_last_adjustment; + + int fb_chan_idx; + uint32_t *fb_dma_buf; + size_t fb_dma_buf_size; + struct dma_config *fb_z_config; + + struct dsrc dsrc; + struct comp_buffer *dsrc_buf; + + int link_id; + int stream_id; +}; +#endif + /** * \brief DAI runtime data */ @@ -148,6 +169,10 @@ struct dai_data { uint64_t wallclock; /* wall clock at stream start */ +#ifdef CONFIG_DAI_INTEL_UAOL + struct uaol_dai_data uaol; +#endif + /* * flag indicating two-step stop/pause for DAI comp and DAI DMA. * DAI stop occurs during STREAM_TRIG_STOP IPC and DMA stop during DAI_CONFIG IPC with diff --git a/src/ipc/ipc4/dai.c b/src/ipc/ipc4/dai.c index 58c08d9ec04b..4e7e43374a2a 100644 --- a/src/ipc/ipc4/dai.c +++ b/src/ipc/ipc4/dai.c @@ -28,6 +28,9 @@ #include #include #include +#ifdef CONFIG_DAI_INTEL_UAOL +#include +#endif #include "../audio/copier/copier.h" #include "../audio/copier/dai_copier.h" @@ -183,8 +186,11 @@ int ipc_dai_data_config(struct dai_data *dd, struct comp_dev *dev) dev->ipc_config.frame_fmt, dd->stream_id); break; +#ifdef CONFIG_DAI_INTEL_UAOL case SOF_DAI_INTEL_UAOL: + dai_get_uaol_stream_id(dd->dai, &dd->uaol.link_id, &dd->uaol.stream_id); break; +#endif default: /* other types of DAIs not handled for now */ comp_warn(dev, "Unknown dai type %d", dai->type); @@ -241,6 +247,14 @@ void dai_dma_release(struct dai_data *dd, struct comp_dev *dev) dma_release_channel(dd->dma->z_dev, dd->chan_index); dd->chan_index = -EINVAL; } + +#ifdef CONFIG_DAI_INTEL_UAOL + if (dd->uaol.fb_chan_idx >= 0) { + dma_stop(dd->dma->z_dev, dd->uaol.fb_chan_idx); + dma_release_channel(dd->dma->z_dev, dd->uaol.fb_chan_idx); + dd->uaol.fb_chan_idx = -EINVAL; + } +#endif } void dai_release_llp_slot(struct dai_data *dd) From f01de31366101291138e2cd8c4184b0a5f84ed5c Mon Sep 17 00:00:00 2001 From: Serhiy Katsyuba Date: Mon, 1 Jun 2026 11:48:23 +0200 Subject: [PATCH 7/7] [***DNM***] west.yml: switch to private Zephyr repo *** DO NOT MERGE *** Switches Zephyr to private repo with UAOL feedback implementation: https://github.com/serhiy-katsyuba-intel/zephyr/tree/uaol_fb2 Signed-off-by: Serhiy Katsyuba --- west.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/west.yml b/west.yml index faf374b914fb..fe9ebf70548d 100644 --- a/west.yml +++ b/west.yml @@ -11,6 +11,8 @@ manifest: url-base: https://github.com/thesofproject - name: zephyrproject url-base: https://github.com/zephyrproject-rtos + - name: sk + url-base: https://github.com/serhiy-katsyuba-intel # When upgrading projects here please run git log --oneline in the # project and if not too long then include the output in your commit @@ -43,8 +45,8 @@ manifest: - name: zephyr repo-path: zephyr - revision: c162980efd9ad8616d0e2fe886ca917d8d8d240a - remote: zephyrproject + revision: uaol_fb2 + remote: sk # Import some projects listed in zephyr/west.yml@revision #