From 30b545750e853a29be88af37df3b225989154df2 Mon Sep 17 00:00:00 2001 From: Serhiy Katsyuba Date: Fri, 11 Sep 2026 11:09:42 +0200 Subject: [PATCH 1/6] [***DNM***] use private Zephyr *** DO NOT MERGE *** This commit will be swapped with proper one once Zephyr PR is merged. Signed-off-by: Serhiy Katsyuba --- west.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/west.yml b/west.yml index 2b23fe5d34ca..d05509d1ef3c 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 @@ -71,8 +73,8 @@ manifest: - name: zephyr repo-path: zephyr - revision: 76280bce2a050376d57e14240687b5c283dea0e7 - remote: zephyrproject + revision: uaol_feedback_0210 + remote: sk # Import some projects listed in zephyr/west.yml@revision # From dfb89101ee4bbab1cd7b12970be837aa5e97b8f8 Mon Sep 17 00:00:00 2001 From: Serhiy Katsyuba Date: Fri, 17 Apr 2026 19:44:55 +0200 Subject: [PATCH 2/6] 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 | 116 +++++++++++++++++++++++++++++++++++ src/include/sof/audio/dsrc.h | 60 ++++++++++++++++++ 2 files changed, 176 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..de1fdcf4c7f6 --- /dev/null +++ b/src/audio/dsrc.c @@ -0,0 +1,116 @@ +/* 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) +{ + uint64_t max_phase = 0; + + if (out_rate > in_rate) + max_phase = ((uint64_t)in_rate << 32) / (out_rate - in_rate); + else + assert(out_rate == in_rate); + + if (max_phase == dsrc->max_phase) + return; + + if (max_phase && dsrc->max_phase) { + uint32_t old_32 = dsrc->max_phase >> 32; + uint32_t new_32 = max_phase >> 32; + + /* Used below as divisors, non-zero as long as out_rate <= 2 * in_rate */ + assert(old_32 && new_32); + + /* Keep the same fractional delay, and phase_acc below the new max_phase */ + dsrc->phase_acc = ((dsrc->phase_acc >> 32) * new_32 / old_32) << 32; + + /* previous_sample_norm is normalized by max_phase, renormalize it */ + for (size_t ch = 0; ch < dsrc->channels; ch++) { + int64_t sample = (dsrc->previous_sample_norm[ch] * old_32) >> 32; + + dsrc->previous_sample_norm[ch] = sample * ((int64_t)1 << 32) / new_32; + } + } else { + /* phase_acc == 0 gives previous samples zero weight */ + dsrc->phase_acc = 0; + } + + dsrc->max_phase = max_phase; +} + +/* 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 = *in_ptr * ((int64_t)1 << 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..3ca6f1401591 --- /dev/null +++ b/src/include/sof/audio/dsrc.h @@ -0,0 +1,60 @@ +/* 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 + +/** + * @brief State of a DSRC (drift-compensating sample rate converter). + * + * Upsamples by a small rate difference: one interpolated frame is inserted + * every in_rate / (out_rate - in_rate) input frames. + */ +struct dsrc { + size_t channels; /**< Number of channels */ + int64_t previous_sample_norm[CONFIG_PLATFORM_MAX_CHANNELS]; /**< Previous sample / max_phase */ + uint64_t phase_acc; /**< Phase accumulator, Q32.32 */ + uint64_t max_phase; /**< Input frames per inserted frame, Q32.32; 0: no resampling */ +}; + +/** + * @brief Initialize struct dsrc. + * + * @param dsrc DSRC state. + * @param channels Number of channels, at most CONFIG_PLATFORM_MAX_CHANNELS. + */ +void dsrc_init(struct dsrc *dsrc, size_t channels); + +/** + * @brief Set the conversion rate. + * + * Equal rates disable resampling and reset the phase. + * + * @param dsrc DSRC state. + * @param in_rate Input rate in Hz. + * @param out_rate Output rate in Hz, in_rate <= out_rate <= 2 * in_rate. + */ +void dsrc_set_rate(struct dsrc *dsrc, uint32_t in_rate, uint32_t out_rate); + +/** + * @brief Resample 32-bit frames. + * + * Neither @p in nor @p out is advanced; the caller consumes @p frames from the input + * and produces @p frames plus the returned number of frames into the output. + * + * @param dsrc DSRC state. + * @param in Input circular buffer position. + * @param out Output circular buffer position, with room for @p frames plus the inserted ones. + * @param frames Number of input frames. + * + * @return Number of extra frames inserted on top of @p frames. + */ +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 cd78d85d5c5e745ab7f4184a681df86c99520a1f Mon Sep 17 00:00:00 2001 From: Serhiy Katsyuba Date: Mon, 27 Apr 2026 18:09:14 +0200 Subject: [PATCH 3/6] 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..1f8556f1ae95 --- /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(const void *src, const void *src_addr, const 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 8846092d8aabc08ba8b169e8abb7b33fbadbf540 Mon Sep 17 00:00:00 2001 From: Serhiy Katsyuba Date: Mon, 21 Sep 2026 15:42:10 +0200 Subject: [PATCH 4/6] audio: dai-zephyr: export dai_get_properties_safe() Make dai_get_properties_safe() non-static and declare it in sof/lib/dai-zephyr.h so that DAI-specific code outside dai-zephyr.c (e.g., intel_uaol.c) can use it. Signed-off-by: Serhiy Katsyuba --- src/audio/dai-zephyr.c | 4 ++-- src/include/sof/lib/dai-zephyr.h | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/audio/dai-zephyr.c b/src/audio/dai-zephyr.c index d9b753f28227..7cdc71220193 100644 --- a/src/audio/dai-zephyr.c +++ b/src/audio/dai-zephyr.c @@ -238,8 +238,8 @@ __cold int dai_set_config(struct dai *dai, struct ipc_config_dai *common_config, return dai_config_set(dev, &cfg, cfg_params, dai_cfg_size); } -static int dai_get_properties_safe(struct dai *dai, int direction, - int stream_id, struct dai_properties *props) +int dai_get_properties_safe(struct dai *dai, int direction, + int stream_id, struct dai_properties *props) { const struct dai_properties *props_p; int ret; diff --git a/src/include/sof/lib/dai-zephyr.h b/src/include/sof/lib/dai-zephyr.h index 0c3febaed73f..081e9359f0a9 100644 --- a/src/include/sof/lib/dai-zephyr.h +++ b/src/include/sof/lib/dai-zephyr.h @@ -262,6 +262,21 @@ void dai_put(struct dai *dai); int dai_set_config(struct dai *dai, struct ipc_config_dai *config, const void *spec_config, size_t size); +/** + * \brief Get a copy of DAI properties + * + * Uses dai_get_properties_copy() when implemented by the driver, falling + * back to a locked dai_get_properties() in kernel LL builds. + * + * \param[in] dai DAI instance + * \param[in] direction Stream direction + * \param[in] stream_id Stream ID + * \param[out] props Destination for the copied properties + * \return 0 on success, negative error code otherwise + */ +int dai_get_properties_safe(struct dai *dai, int direction, + int stream_id, struct dai_properties *props); + /** * \brief Get Digital Audio interface DMA Handshake */ From 3da5e52b1f3d27c0391a8a4b0951de195ae1d2c5 Mon Sep 17 00:00:00 2001 From: Serhiy Katsyuba Date: Tue, 10 Mar 2026 11:22:50 +0100 Subject: [PATCH 5/6] 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 | 171 ++++++++++++-- src/audio/intel_uaol.c | 356 +++++++++++++++++++++++++++++ src/include/sof/audio/intel_uaol.h | 21 ++ src/include/sof/lib/dai-zephyr.h | 35 +++ src/ipc/ipc4/dai.c | 41 +++- 6 files changed, 606 insertions(+), 20 deletions(-) diff --git a/src/audio/CMakeLists.txt b/src/audio/CMakeLists.txt index 0dd1b1694381..e975a61c7a06 100644 --- a/src/audio/CMakeLists.txt +++ b/src/audio/CMakeLists.txt @@ -150,7 +150,7 @@ if(CONFIG_COMP_TONE) add_subdirectory(tone) endif() if(CONFIG_UAOL_INTEL_ADSP) - add_local_sources(sof intel_uaol.c) + add_local_sources(sof intel_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 7cdc71220193..c0f04700628b 100644 --- a/src/audio/dai-zephyr.c +++ b/src/audio/dai-zephyr.c @@ -46,6 +46,10 @@ #include #include +#ifdef CONFIG_UAOL_INTEL_ADSP +#include +#include +#endif #include @@ -208,6 +212,7 @@ __cold int dai_set_config(struct dai *dai, struct ipc_config_dai *common_config, cfg.type = DAI_AMD_TDM; cfg_params = &sof_cfg->acptdm; break; +#ifdef CONFIG_UAOL_INTEL_ADSP case SOF_DAI_INTEL_UAOL: cfg.type = DAI_INTEL_UAOL; cfg.channels = common_config->gtw_fmt->channels_count; @@ -219,7 +224,9 @@ __cold int dai_set_config(struct dai *dai, struct ipc_config_dai *common_config, cfg.word_size = common_config->gtw_fmt->valid_bit_depth; cfg_params = spec_config; dai_set_link_hda_config(&cfg.link_config, common_config, spec_config); + cfg.extra_link_config = dai_uaol_feedback_link_config(common_config); break; +#endif /* CONFIG_UAOL_INTEL_ADSP */ case SOF_DAI_MEDIATEK_AFE: cfg.type = DAI_MEDIATEK_AFE; cfg_params = &sof_cfg->afe; @@ -396,8 +403,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_UAOL_INTEL_ADSP + if (dd->uaol.feedback_drift) + ret = uaol_dma_buffer_copy_to(dd, bytes); + else +#endif /* CONFIG_UAOL_INTEL_ADSP */ + 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); /* @@ -479,6 +492,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_UAOL_INTEL_ADSP + if (dd->uaol.fb_chan_idx >= 0) + process_uaol_feedback(dev, dd); +#endif /* CONFIG_UAOL_INTEL_ADSP */ + #ifdef CONFIG_SOF_TELEMETRY_IO_PERFORMANCE_MEASUREMENTS /* Increment performance counters */ io_perf_monitor_update_data(dd->io_perf_dai_byte_count, bytes); @@ -569,6 +588,10 @@ __cold int dai_common_new(struct dai_data *dd, struct comp_dev *dev, SOF_DMA_DIR_MEM_TO_DEV : SOF_DMA_DIR_DEV_TO_MEM; dd->chan_index = -EINVAL; +#ifdef CONFIG_UAOL_INTEL_ADSP + dd->uaol.fb_chan_idx = -EINVAL; +#endif + dd->dma = sof_dma_get(dir, dd->dai->dma_caps, dd->dai->dma_dev, SOF_DMA_ACCESS_SHARED); if (!dd->dma) { dai_put(dd->dai); @@ -637,6 +660,84 @@ __cold int dai_common_new(struct dai_data *dd, struct comp_dev *dev, return 0; } +/* UAOL playback may own a 2nd DMA channel reading the USB feedback endpoint; + * it follows the audio channel through config/start/stop/suspend/release. + */ +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 + /* also release the UAOL feedback DMA channel */ + if (dd->uaol.fb_chan_idx >= 0) { + sof_dma_release_channel(dd->uaol.fb_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 + /* also config the UAOL feedback DMA channel */ + if (dd->uaol.fb_chan_idx >= 0) + ret = sof_dma_config(dd->uaol.fb_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 + /* also start the UAOL feedback DMA channel; roll back audio DMA on failure */ + if (dd->uaol.fb_chan_idx >= 0) { + ret = sof_dma_start(dd->uaol.fb_dma, dd->uaol.fb_chan_idx); + if (ret < 0) + sof_dma_stop(dd->dma, dd->chan_index); + } +#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 + /* also stop the UAOL feedback DMA channel, even if the above fails */ + if (dd->uaol.fb_chan_idx >= 0) + sof_dma_stop(dd->uaol.fb_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 + /* also suspend the UAOL feedback DMA channel, even if the above fails */ + if (dd->uaol.fb_chan_idx >= 0) + sof_dma_suspend(dd->uaol.fb_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) @@ -693,11 +794,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); @@ -705,6 +802,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) @@ -1187,8 +1288,11 @@ 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; + } + out: /* * Make sure to free all allocated items, all functions @@ -1254,6 +1358,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_UAOL_INTEL_ADSP + /* Does nothing if feedback DMA is not needed */ + setup_uaol_feedback_dma(dd, dev); +#endif /* CONFIG_UAOL_INTEL_ADSP */ + return 0; } @@ -1277,6 +1386,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_UAOL_INTEL_ADSP + if (dd->uaol.fb_dma_buf) + memset(dd->uaol.fb_dma_buf, 0, dd->uaol.fb_dma_buf_size); +#endif /* CONFIG_UAOL_INTEL_ADSP */ /* dma reconfig not required if XRUN handling */ if (dd->xrun) { @@ -1285,7 +1398,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); @@ -1336,6 +1449,10 @@ void dai_common_reset(struct dai_data *dd, struct comp_dev *dev) dd->dma_buffer = NULL; } +#ifdef CONFIG_UAOL_INTEL_ADSP + uaol_free(dd); +#endif /* CONFIG_UAOL_INTEL_ADSP */ + dd->wallclock = 0; dd->total_data_processed = 0; dd->xrun = 0; @@ -1372,7 +1489,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; @@ -1393,6 +1510,14 @@ static int dai_comp_trigger_internal(struct dai_data *dd, struct comp_dev *dev, buffer_zero(dd->dma_buffer); } +#ifdef CONFIG_UAOL_INTEL_ADSP + /* 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_UAOL_INTEL_ADSP */ + /* 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 @@ -1410,16 +1535,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; @@ -1447,11 +1572,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; @@ -1461,11 +1586,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: @@ -1800,6 +1925,11 @@ int dai_common_copy(struct dai_data *dd, struct comp_dev *dev, pcm_converter_fun if (dev->direction == SOF_IPC_STREAM_PLAYBACK) { src_frames = audio_stream_get_avail_frames(&dd->local_buffer->stream); sink_frames = free_bytes / audio_stream_frame_bytes(&dd->dma_buffer->stream); +#ifdef CONFIG_UAOL_INTEL_ADSP + /* DSRC may insert one frame on top of the copied ones */ + if (dd->uaol.feedback_drift > 0 && sink_frames) + sink_frames--; +#endif frames = MIN(src_frames, sink_frames); /* @@ -1896,8 +2026,13 @@ 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); +#ifdef CONFIG_UAOL_INTEL_ADSP + /* adjust reload size as extra frame may be added by DSRC */ + copy_bytes += dd->uaol.dma_added_bytes; + dd->uaol.dma_added_bytes = 0; +#endif ret = sof_dma_reload(dd->dma, dd->chan_index, copy_bytes); if (ret < 0) { dai_report_reload_xrun(dd, dev, copy_bytes); diff --git a/src/audio/intel_uaol.c b/src/audio/intel_uaol.c index baeb4a217103..327f204bc215 100644 --- a/src/audio/intel_uaol.c +++ b/src/audio/intel_uaol.c @@ -4,10 +4,13 @@ */ #include +#include #include #include #include +#include #include +#include "copier/copier.h" LOG_MODULE_REGISTER(uaol, CONFIG_SOF_LOG_LEVEL); @@ -77,3 +80,356 @@ __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) +{ + struct dai_properties props; + int ret; + + ret = dai_get_properties_safe(dai, 0, 0, &props); + if (ret < 0) + return ret; + + *uaol_link_id = props.uaol_link_id; + *uaol_stream_id = props.uaol_stream_id; + + 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->uaol.fb_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) + return; /* No feedback is normal as it might be sent rarely. */ + + 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->uaol.fb_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_dbg(dev, "Weird UAOL feedback freq value: %d, drift: %d. Rejected!", + freq, drift); + return; + } + if (drift < -CLIP_DRIFT_HZ || drift > CLIP_DRIFT_HZ) { + comp_dbg(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; + /* DSRC only works with drift >= 0; 0 also resets it, so no stale history is reused */ + dsrc_set_rate(&dd->uaol.dsrc, dd->ipc_config.sampling_frequency, + dd->ipc_config.sampling_frequency + MAX(drift, 0)); +} + +/* 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 dma_bytes) +{ + int ret = 0; + + assert(dd->uaol.feedback_drift != 0); + + if (dd->uaol.feedback_drift > 0) { + size_t frames = dma_bytes / audio_stream_frame_bytes(&dd->dma_buffer->stream); + size_t src_bytes = frames * audio_stream_frame_bytes(&dd->local_buffer->stream); + + buffer_stream_invalidate(dd->local_buffer, src_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 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, src_bytes); + audio_stream_produce(&dd->uaol.dsrc_buf->stream, src_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, dma_bytes + dst_added_bytes, dd->chmap); + dd->uaol.dma_added_bytes = dst_added_bytes; + } 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 (dd->uaol.ms_since_last_adjustment >= -1000 / dd->uaol.feedback_drift) { + 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, dma_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; + uint32_t addr_align; + int ret; + + dd->uaol.fb_chan_idx = -EINVAL; + dd->uaol.feedback_drift = 0; + dd->uaol.ms_since_last_adjustment = 0; + dd->uaol.dma_added_bytes = 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); + + /* copier input format, i.e. local_buffer format for playback */ + const struct ipc4_audio_format *local_fmt = + &((const struct ipc4_copier_module_cfg *)dd->dai_spec_config)->base.audio_fmt; + + if (local_fmt->depth != 32) { + comp_err(dev, "UAOL feedback disabled: DSRC supports only 32-bit samples, got %d", + local_fmt->depth); + return -EINVAL; + } + + if (!dd->uaol.fb_dma) { + dd->uaol.fb_dma = sof_dma_get(SOF_DMA_DIR_DEV_TO_MEM, dd->dai->dma_caps, + dd->dai->dma_dev, SOF_DMA_ACCESS_SHARED); + if (!dd->uaol.fb_dma) { + comp_err(dev, "dma_get() failed to get the UAOL feedback DMA"); + return -ENODEV; + } + } + + /* 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; + + ret = sof_dma_get_attribute(dd->uaol.fb_dma, DMA_ATTR_BUFFER_ADDRESS_ALIGNMENT, + &addr_align); + if (ret < 0) { + comp_err(dev, "can't get UAOL feedback dma buffer addr align, err = %d", ret); + return ret; + } + + /* DMA-written and read through an uncached pointer, see process_uaol_feedback() */ + dd->uaol.fb_dma_buf = sof_heap_alloc(dd->alloc_ctx.heap, + SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT | + SOF_MEM_FLAG_DMA, + dd->uaol.fb_dma_buf_size, addr_align); + 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 = sof_heap_alloc(dd->alloc_ctx.heap, + SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT | SOF_MEM_FLAG_DMA, + sizeof(struct dma_config), 0); + if (!dma_cfg) { + sof_heap_free(dd->alloc_ctx.heap, 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 = sof_heap_alloc(dd->alloc_ctx.heap, + SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT | + SOF_MEM_FLAG_DMA, + sizeof(struct dma_block_config), 0); + if (!dma_cfg->head_block) { + sof_heap_free(dd->alloc_ctx.heap, dma_cfg); + sof_heap_free(dd->alloc_ctx.heap, 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; + 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; + + /* dai_common_params() must have created the DMA buffer already */ + assert(dd->dma_buffer); + /* dai_common_copy() keeps one free DMA frame for the DSRC-inserted one, so a copy + * never exceeds the DMA buffer size in frames + */ + size_t dma_frames = audio_stream_get_size(&dd->dma_buffer->stream) / + audio_stream_frame_bytes(&dd->dma_buffer->stream); + size_t dsrc_buf_size = dma_frames * local_fmt->channels_count * (local_fmt->depth / 8); + + dd->uaol.dsrc_buf = buffer_alloc_range(&dd->alloc_ctx, 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"); + sof_heap_free(dd->alloc_ctx.heap, dma_cfg->head_block); + sof_heap_free(dd->alloc_ctx.heap, dma_cfg); + sof_heap_free(dd->alloc_ctx.heap, dd->uaol.fb_dma_buf); + dd->uaol.fb_dma_buf = NULL; + dd->uaol.fb_dma_buf_size = 0; + return -ENOMEM; + } + ipc4_update_buffer_format(dd->uaol.dsrc_buf, local_fmt); + + dd->uaol.fb_chan_idx = sof_dma_request_channel(dd->uaol.fb_dma, channel); + if (dd->uaol.fb_chan_idx < 0) { + comp_err(dev, "UAOL feedback dma_request_channel(%d) failed: %d", + channel, dd->uaol.fb_chan_idx); + buffer_free(dd->uaol.dsrc_buf); + dd->uaol.dsrc_buf = NULL; + sof_heap_free(dd->alloc_ctx.heap, dma_cfg->head_block); + sof_heap_free(dd->alloc_ctx.heap, dma_cfg); + sof_heap_free(dd->alloc_ctx.heap, dd->uaol.fb_dma_buf); + dd->uaol.fb_dma_buf = NULL; + dd->uaol.fb_dma_buf_size = 0; + dd->uaol.fb_chan_idx = -EINVAL; + return -EIO; + } + + dd->uaol.fb_z_config = dma_cfg; + + dsrc_init(&dd->uaol.dsrc, local_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) { + sof_heap_free(dd->alloc_ctx.heap, dd->uaol.fb_z_config->head_block); + sof_heap_free(dd->alloc_ctx.heap, dd->uaol.fb_z_config); + dd->uaol.fb_z_config = NULL; + } + + if (dd->uaol.fb_dma_buf) { + sof_heap_free(dd->alloc_ctx.heap, 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; + } + + if (dd->uaol.fb_dma) { + sof_dma_put(dd->uaol.fb_dma); + dd->uaol.fb_dma = NULL; + } +} diff --git a/src/include/sof/audio/intel_uaol.h b/src/include/sof/audio/intel_uaol.h index e8964d2d7450..d36335baf068 100644 --- a/src/include/sof/audio/intel_uaol.h +++ b/src/include/sof/audio/intel_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 dma_bytes); + +int setup_uaol_feedback_dma(struct dai_data *dd, struct comp_dev *dev); + +void uaol_free(struct dai_data *dd); + #endif /* __SOF_AUDIO_INTEL_UAOL_H__ */ diff --git a/src/include/sof/lib/dai-zephyr.h b/src/include/sof/lib/dai-zephyr.h index 081e9359f0a9..defab09317f9 100644 --- a/src/include/sof/lib/dai-zephyr.h +++ b/src/include/sof/lib/dai-zephyr.h @@ -29,6 +29,9 @@ #include #include #include +#ifdef CONFIG_UAOL_INTEL_ADSP +#include +#endif #include #include #include @@ -113,6 +116,28 @@ 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_UAOL_INTEL_ADSP +struct uaol_dai_data { + int feedback_drift; + uint32_t ms_since_last_adjustment; + + /* the feedback flows link to memory, unlike the playback data on dd->dma */ + struct sof_dma *fb_dma; + 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; + /* bytes DSRC wrote to dma_buffer on top of the last copy size */ + uint32_t dma_added_bytes; + + int link_id; + int stream_id; +}; +#endif + /** * \brief DAI runtime data */ @@ -147,6 +172,10 @@ struct dai_data { uint64_t wallclock; /* wall clock at stream start */ +#ifdef CONFIG_UAOL_INTEL_ADSP + 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 @@ -308,6 +337,12 @@ int dai_config_dma_channel(struct dai_data *dd, struct comp_dev *dev, const void void dai_set_link_hda_config(uint16_t *link_config, struct ipc_config_dai *common_config, const void *spec_config); + +/** + * \brief Get HD Audio link config of the UAOL feedback stream + * \return the link config, or 0 when the host supplied no feedback DMA link + */ +uint16_t dai_uaol_feedback_link_config(const struct ipc_config_dai *common_config); /** * \brief Reset DAI DMA config */ diff --git a/src/ipc/ipc4/dai.c b/src/ipc/ipc4/dai.c index 27c8ee8b6f56..ef1c7bd37d00 100644 --- a/src/ipc/ipc4/dai.c +++ b/src/ipc/ipc4/dai.c @@ -30,6 +30,9 @@ #include #include #include +#ifdef CONFIG_UAOL_INTEL_ADSP +#include +#endif #include "../audio/copier/copier.h" #include "../audio/copier/dai_copier.h" @@ -79,6 +82,23 @@ void dai_set_link_hda_config(uint16_t *link_config, #endif /* ACE_VERSION > ACE_VERSION_1_5 */ } +#ifdef CONFIG_UAOL_INTEL_ADSP +/* Link config of the UAOL feedback stream: a single 3-byte sample per packet */ +uint16_t dai_uaol_feedback_link_config(const struct ipc_config_dai *common_config) +{ + union hdalink_cfg link_cfg = { .full = 0 }; + const struct ipc_dma_config *fb_dma = common_config->host_dma_config[1]; + + if (common_config->direction != SOF_IPC_STREAM_PLAYBACK || !fb_dma || + !fb_dma->pre_allocated_by_host) + return 0; + + link_cfg.part.stream = fb_dma->stream_id; + + return link_cfg.full; +} +#endif + int dai_config_dma_channel(struct dai_data *dd, struct comp_dev *dev, const void *spec_config) { const struct ipc4_copier_module_cfg *copier_cfg = spec_config; @@ -185,8 +205,18 @@ int ipc_dai_data_config(struct dai_data *dd, struct comp_dev *dev) dev->ipc_config.frame_fmt, dd->stream_id); break; - case SOF_DAI_INTEL_UAOL: +#ifdef CONFIG_UAOL_INTEL_ADSP + case SOF_DAI_INTEL_UAOL: { + int ret = dai_get_uaol_stream_id(dd->dai, &dd->uaol.link_id, + &dd->uaol.stream_id); + + if (ret < 0) { + comp_err(dev, "Failed to get UAOL stream id: %d", ret); + return ret; + } break; + } +#endif default: /* other types of DAIs not handled for now */ comp_warn(dev, "Unknown dai type %d", dai->type); @@ -243,6 +273,15 @@ void dai_dma_release(struct dai_data *dd, struct comp_dev *dev) sof_dma_release_channel(dd->dma, dd->chan_index); dd->chan_index = -EINVAL; } + +#ifdef CONFIG_UAOL_INTEL_ADSP + /* also stop and release the UAOL feedback DMA channel */ + if (dd->uaol.fb_chan_idx >= 0) { + sof_dma_stop(dd->uaol.fb_dma, dd->uaol.fb_chan_idx); + sof_dma_release_channel(dd->uaol.fb_dma, dd->uaol.fb_chan_idx); + dd->uaol.fb_chan_idx = -EINVAL; + } +#endif } void dai_release_llp_slot(struct dai_data *dd) From a1d555e7e0f03f50bb424c6a4eb67ed7c69a6687 Mon Sep 17 00:00:00 2001 From: Serhiy Katsyuba Date: Wed, 28 Oct 2026 16:25:32 +0100 Subject: [PATCH 6/6] uaol: support UAOL feedback with user-space LL With SOF_USERSPACE_LL, the LL thread processing the UAOL feedback runs in user space and calls uaol_interpret_feedback_value() and uaol_adjust_rate() on the UAOL link devices. - Select UAOL_USERSPACE to turn those UAOL driver calls into syscalls. - Grant the LL thread access to the UAOL link devices, as done for DAI and DMA devices. - Make the uaol_devs[] pointers const, so the array is placed in rodata, which the user-space LL thread can read. Signed-off-by: Serhiy Katsyuba --- src/audio/intel_uaol.c | 3 ++- src/schedule/zephyr_domain.c | 1 + zephyr/Kconfig | 1 + zephyr/include/rtos/userspace_helper.h | 7 +++++++ zephyr/lib/userspace_helper.c | 12 ++++++++++++ 5 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/audio/intel_uaol.c b/src/audio/intel_uaol.c index 327f204bc215..316971dc314a 100644 --- a/src/audio/intel_uaol.c +++ b/src/audio/intel_uaol.c @@ -29,7 +29,8 @@ struct ipc4_uaol_capabilities { } __packed __aligned(4); #define DEV_AND_COMMA(node) DEVICE_DT_GET(node), -static const struct device *uaol_devs[] = { +/* const pointers keep the array in rodata, readable by the user-space LL thread */ +static const struct device *const uaol_devs[] = { DT_FOREACH_STATUS_OKAY(intel_adsp_uaol, DEV_AND_COMMA) }; diff --git a/src/schedule/zephyr_domain.c b/src/schedule/zephyr_domain.c index 5ceee36fa98a..9e10212a1cda 100644 --- a/src/schedule/zephyr_domain.c +++ b/src/schedule/zephyr_domain.c @@ -363,6 +363,7 @@ static int zephyr_domain_thread_init(struct ll_schedule_domain *domain, k_thread_access_grant(thread, dt->sem, domain->lock, zephyr_domain->timer); user_grant_dai_access_all(thread); user_grant_dma_access_all(thread); + user_grant_uaol_access_all(thread); tr_dbg(&ll_tr, "granted LL access to thread %p (core %d)", thread, core); k_thread_start(thread); diff --git a/zephyr/Kconfig b/zephyr/Kconfig index 867100623b42..5cc4c05427c9 100644 --- a/zephyr/Kconfig +++ b/zephyr/Kconfig @@ -50,6 +50,7 @@ config SOF_USERSPACE_LL select SOF_USERSPACE_INTERFACE_ALLOC select SOF_USERSPACE_INTERFACE_DMA select SOF_USERSPACE_INTERFACE_VREGION if SOF_VREGIONS + select UAOL_USERSPACE if UAOL_INTEL_ADSP help Run Low-Latency (LL) pipelines in userspace threads. This adds memory protection between operating system resources and diff --git a/zephyr/include/rtos/userspace_helper.h b/zephyr/include/rtos/userspace_helper.h index 44d03b827bd3..5ec8b18e016c 100644 --- a/zephyr/include/rtos/userspace_helper.h +++ b/zephyr/include/rtos/userspace_helper.h @@ -125,6 +125,13 @@ void user_grant_dai_access_all(struct k_thread *thread); */ void user_grant_dma_access_all(struct k_thread *thread); +/** + * Grant UAOL device access to a user-space thread. + * + * @param thread user-space thread for which UAOL access is granted + */ +void user_grant_uaol_access_all(struct k_thread *thread); + #else /** diff --git a/zephyr/lib/userspace_helper.c b/zephyr/lib/userspace_helper.c index e205fc256308..370ab64e4858 100644 --- a/zephyr/lib/userspace_helper.c +++ b/zephyr/lib/userspace_helper.c @@ -25,6 +25,8 @@ /* Zephyr includes */ #include +#include +#include #include #include @@ -186,6 +188,16 @@ void user_grant_dma_access_all(struct k_thread *thread) } } +void user_grant_uaol_access_all(struct k_thread *thread) +{ +#ifdef CONFIG_UAOL_INTEL_ADSP +#define UAOL_GRANT(node) k_object_access_grant(DEVICE_DT_GET(node), thread); + DT_FOREACH_STATUS_OKAY(intel_adsp_uaol, UAOL_GRANT) +#undef UAOL_GRANT + LOG_DBG("Granted UAOL access to thread %p", thread); +#endif +} + #endif /* CONFIG_SOF_USERSPACE_LL */ #else /* CONFIG_USERSPACE */