diff --git a/.github/workflows/testbench.yml b/.github/workflows/testbench.yml index 290fe8e56dee..d54b1db331db 100644 --- a/.github/workflows/testbench.yml +++ b/.github/workflows/testbench.yml @@ -49,12 +49,27 @@ jobs: octave octave-signal automake autoconf libtool gettext libasound2-dev + # Ubuntu 24.04 ships autoconf 2.71, but current alsa-lib/alsa-utils + # require >= 2.72. Build a newer autoconf from the GNU release + # tarball and install it into /usr/local (ahead of /usr/bin in PATH). + - name: Install autoconf 2.72 + run: | + cd ${GITHUB_WORKSPACE} + wget -q https://ftp.gnu.org/gnu/autoconf/autoconf-2.72.tar.gz + tar xf autoconf-2.72.tar.gz + cd autoconf-2.72 + ./configure --prefix=/usr/local + make -j"$(nproc)" + sudo make install + hash -r + autoconf --version | head -n1 + - name: Build Alsa-lib run: | cd ${GITHUB_WORKSPACE} git clone https://github.com/thesofproject/alsa-lib.git cd alsa-lib - git checkout df8f1cc1ec9d9ee15be5e2c23ad25b9389fd8766 -b build + git checkout 08b532cd3da9ac8f683bcb4e4beb9b74c39c1782 -b build ./gitcompile --prefix=${GITHUB_WORKSPACE}/tools make install @@ -63,7 +78,7 @@ jobs: cd ${GITHUB_WORKSPACE} git clone https://github.com/thesofproject/alsa-utils.git cd alsa-utils - git checkout 0ffa105942a06cdfa98e5918b8dc82e3cac12792 -b build + git checkout 9feb22ba45b48729729c8d194aaf1bc082a6842a -b build ./gitcompile --prefix=${GITHUB_WORKSPACE}/tools \ --with-alsa-prefix=${GITHUB_WORKSPACE}/tools \ --with-alsa-inc-prefix=${GITHUB_WORKSPACE}/tools/include \ diff --git a/.github/workflows/tools.yml b/.github/workflows/tools.yml index 7c70e93ddb5f..825b571321d8 100644 --- a/.github/workflows/tools.yml +++ b/.github/workflows/tools.yml @@ -13,25 +13,71 @@ permissions: jobs: # This is not the same as building every ./build-tools.sh option. top-level_default_CMake_target_ALL: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 with: filter: 'tree:0' + path: sof + + - name: apt get + run: sudo apt-get update && + sudo apt-get -y install ninja-build + automake autoconf libtool gettext libasound2-dev + pkg-config + + # Ubuntu 24.04 ships autoconf 2.71, but current alsa-lib/alsa-utils + # require >= 2.72. Build a newer autoconf from the GNU release + # tarball and install it into /usr/local (ahead of /usr/bin in PATH). + - name: Install autoconf 2.72 + run: | + cd ${GITHUB_WORKSPACE} + wget -q https://ftp.gnu.org/gnu/autoconf/autoconf-2.72.tar.gz + tar xf autoconf-2.72.tar.gz + cd autoconf-2.72 + ./configure --prefix=/usr/local + make -j"$(nproc)" + sudo make install + hash -r + autoconf --version | head -n1 + + - name: Build Alsa-lib + run: | + cd ${GITHUB_WORKSPACE} + git clone https://github.com/thesofproject/alsa-lib.git + cd alsa-lib + git checkout 08b532cd3da9ac8f683bcb4e4beb9b74c39c1782 -b build + ./gitcompile --prefix=${GITHUB_WORKSPACE}/tools + make install - # The ALSA version in Ubuntu 20.04 is buggy - # (https://github.com/thesofproject/sof/issues/2543) and likely - # getting out of date soon - - name: docker - run: docker pull thesofproject/sof && docker tag thesofproject/sof sof + - name: Build Alsa-utils + run: | + cd ${GITHUB_WORKSPACE} + git clone https://github.com/thesofproject/alsa-utils.git + cd alsa-utils + git checkout 9feb22ba45b48729729c8d194aaf1bc082a6842a -b build + ./gitcompile --prefix=${GITHUB_WORKSPACE}/tools \ + --with-alsa-prefix=${GITHUB_WORKSPACE}/tools \ + --with-alsa-inc-prefix=${GITHUB_WORKSPACE}/tools/include \ + --with-sysroot=${GITHUB_WORKSPACE}/tools \ + --with-udev-rules-dir=${GITHUB_WORKSPACE}/tools \ + PKG_CONFIG_PATH=${GITHUB_WORKSPACE}/tools \ + LDFLAGS=-L${GITHUB_WORKSPACE}/tools/lib \ + --disable-old-symbols \ + --enable-alsatopology \ + --with-asound-state-dir=${GITHUB_WORKSPACE}/tools/var/lib/alsa \ + --with-systemdsystemunitdir=${GITHUB_WORKSPACE}/tools/lib/systemd/system + make install # For some reason gcc has more warnings in Release mode - name: build-tools - run: CMAKE_BUILD_TYPE=Release ./scripts/docker-run.sh - ./scripts/build-tools.sh || + env: + PKG_CONFIG_PATH: ${{ github.workspace }}/tools/lib/pkgconfig + LD_LIBRARY_PATH: ${{ github.workspace }}/tools/lib + PATH: ${{ github.workspace }}/tools/bin:/usr/local/bin:/usr/bin:/bin + run: CMAKE_BUILD_TYPE=Release ./sof/scripts/build-tools.sh || VERBOSE=1 NO_PROCESSORS=1 USE_XARGS=no - CMAKE_BUILD_TYPE=Release ./scripts/docker-run.sh - ./scripts/build-tools.sh + CMAKE_BUILD_TYPE=Release ./sof/scripts/build-tools.sh SOF-alsa-plugin: diff --git a/app/boards/acp_7_0_adsp.conf b/app/boards/acp_7_0_adsp.conf index 3c5f791fdcb6..5eb2ff9f7b95 100644 --- a/app/boards/acp_7_0_adsp.conf +++ b/app/boards/acp_7_0_adsp.conf @@ -14,6 +14,8 @@ CONFIG_INTC_AMD_ACP=y CONFIG_DMA_AMD_ACP_HOST=y CONFIG_DMA_AMD_ACP_SDW=y CONFIG_DAI_AMD_SDW=y +CONFIG_DMA_AMD_ACP_TDM=y +CONFIG_DAI_AMD_TDM=y CONFIG_AMS=n CONFIG_WRAP_ACTUAL_POSITION=y CONFIG_TRACE=n @@ -50,3 +52,4 @@ CONFIG_ASRC_SUPPORT_CONVERSION_48000_TO_32000=n CONFIG_ASRC_SUPPORT_CONVERSION_48000_TO_44100=n CONFIG_CORE_COUNT=1 CONFIG_FORMAT_CONVERT_HIFI3=n +CONFIG_COMP_MFCC=n \ No newline at end of file diff --git a/app/boards/acp_7_x_adsp.conf b/app/boards/acp_7_x_adsp.conf index 0ec10fff2bf7..04c63271c9ac 100644 --- a/app/boards/acp_7_x_adsp.conf +++ b/app/boards/acp_7_x_adsp.conf @@ -14,7 +14,9 @@ CONFIG_INTC_AMD_ACP=y CONFIG_DMA_AMD_ACP_HOST=y CONFIG_DMA_AMD_ACP_SDW=y CONFIG_DMA_AMD_ACP_SDW_CHANNEL_COUNT=64 +CONFIG_DMA_AMD_ACP_TDM=y CONFIG_DAI_AMD_SDW=y +CONFIG_DAI_AMD_TDM=y CONFIG_AMS=n CONFIG_WRAP_ACTUAL_POSITION=y CONFIG_TRACE=n @@ -52,3 +54,4 @@ CONFIG_ASRC_SUPPORT_CONVERSION_48000_TO_44100=n CONFIG_CORE_COUNT=1 CONFIG_FORMAT_CONVERT_HIFI3=n CONFIG_PCM_CONVERTER_FORMAT_S24_4LE_MSB=y +CONFIG_XTENSA_INTERRUPT_NONPREEMPTABLE=y diff --git a/app/boards/imx95_evk_mimx9596_m7_ddr.overlay b/app/boards/imx95_evk_mimx9596_m7_ddr.overlay index f1d2cce00781..4c9ce43c9452 100644 --- a/app/boards/imx95_evk_mimx9596_m7_ddr.overlay +++ b/app/boards/imx95_evk_mimx9596_m7_ddr.overlay @@ -26,6 +26,10 @@ status = "okay"; }; +&edma1 { + status = "disabled"; +}; + &edma2 { compatible = "nxp,edma"; reg = <0x42000000 (DT_SIZE_K(64) * 33)>; diff --git a/app/boards/intel_adsp_ace15_mtpm.conf b/app/boards/intel_adsp_ace15_mtpm.conf index 4f124eee5285..0b5472cd282a 100644 --- a/app/boards/intel_adsp_ace15_mtpm.conf +++ b/app/boards/intel_adsp_ace15_mtpm.conf @@ -15,6 +15,7 @@ CONFIG_COMP_MFCC=y CONFIG_COMP_MULTIBAND_DRC=y CONFIG_FORMAT_CONVERT_HIFI3=n CONFIG_SAMPLE_KEYPHRASE=y +CONFIG_COMP_PHASE_VOCODER=y CONFIG_COMP_STFT_PROCESS=y # SOF / audio modules / mocks @@ -55,6 +56,7 @@ CONFIG_HEAP_MEM_POOL_SIZE=8192 CONFIG_LLEXT=y CONFIG_LLEXT_STORAGE_WRITABLE=y CONFIG_LLEXT_EXPERIMENTAL=y +CONFIG_LLEXT_EDK=n CONFIG_MODULES=y CONFIG_TIMING_FUNCTIONS=y CONFIG_WATCHDOG=y diff --git a/app/boards/intel_adsp_ace20_lnl.conf b/app/boards/intel_adsp_ace20_lnl.conf index 2aef09fb9a4a..c0b225650c01 100644 --- a/app/boards/intel_adsp_ace20_lnl.conf +++ b/app/boards/intel_adsp_ace20_lnl.conf @@ -11,6 +11,7 @@ CONFIG_COMP_TESTER=m CONFIG_COMP_SRC_IPC4_FULL_MATRIX=y CONFIG_FORMAT_CONVERT_HIFI3=n CONFIG_SAMPLE_KEYPHRASE=y +CONFIG_COMP_PHASE_VOCODER=y CONFIG_COMP_STFT_PROCESS=y # SOF / infrastructure @@ -38,6 +39,7 @@ CONFIG_HEAP_MEM_POOL_SIZE=8192 CONFIG_LLEXT=y CONFIG_LLEXT_STORAGE_WRITABLE=y CONFIG_LLEXT_EXPERIMENTAL=y +CONFIG_LLEXT_EDK=n CONFIG_MODULES=y CONFIG_TIMING_FUNCTIONS=y diff --git a/app/boards/intel_adsp_ace30_ptl.conf b/app/boards/intel_adsp_ace30_ptl.conf index 20e866ade585..b6ac41938398 100644 --- a/app/boards/intel_adsp_ace30_ptl.conf +++ b/app/boards/intel_adsp_ace30_ptl.conf @@ -42,6 +42,7 @@ CONFIG_HEAP_MEM_POOL_SIZE=8192 CONFIG_LLEXT=y CONFIG_LLEXT_STORAGE_WRITABLE=y CONFIG_LLEXT_EXPERIMENTAL=y +CONFIG_LLEXT_EDK=n CONFIG_MODULES=y # Zephyr / device drivers diff --git a/app/boards/intel_adsp_ace30_wcl.conf b/app/boards/intel_adsp_ace30_wcl.conf index 999a3c309d41..2196af333e65 100644 --- a/app/boards/intel_adsp_ace30_wcl.conf +++ b/app/boards/intel_adsp_ace30_wcl.conf @@ -28,6 +28,7 @@ CONFIG_COLD_STORE_EXECUTE_DRAM=y # SOF / loadable modules CONFIG_INTEL_MODULES=y CONFIG_LIBRARY_MANAGER=y +CONFIG_LIBRARY_AUTH_SUPPORT=y CONFIG_LIBRARY_BASE_ADDRESS=0xa0688000 CONFIG_LIBRARY_BUILD_LIB=y CONFIG_LIBRARY_DEFAULT_MODULAR=y @@ -40,6 +41,7 @@ CONFIG_HEAP_MEM_POOL_SIZE=8192 CONFIG_LLEXT=y CONFIG_LLEXT_STORAGE_WRITABLE=y CONFIG_LLEXT_EXPERIMENTAL=y +CONFIG_LLEXT_EDK=n CONFIG_MODULES=y # Zephyr / device drivers diff --git a/app/boards/intel_adsp_ace40_nvl.conf b/app/boards/intel_adsp_ace40_nvl.conf index becef1a65f10..bb39cf026ada 100644 --- a/app/boards/intel_adsp_ace40_nvl.conf +++ b/app/boards/intel_adsp_ace40_nvl.conf @@ -12,6 +12,12 @@ CONFIG_COMP_SRC_IPC4_FULL_MATRIX=y CONFIG_COMP_TESTER=y CONFIG_FORMAT_CONVERT_HIFI3=n +# SOF / audio modules / mocks +# This mock is part of official sof-bin releases because the CI that +# tests it can't use extra CONFIGs. See #9410, #8722 and #9386 +CONFIG_COMP_GOOGLE_RTC_AUDIO_PROCESSING=m +CONFIG_GOOGLE_RTC_AUDIO_PROCESSING_MOCK=y + # SOF / infrastructure CONFIG_PROBE=y CONFIG_PROBE_DMA_MAX=2 @@ -33,6 +39,7 @@ CONFIG_HEAP_MEM_POOL_SIZE=8192 CONFIG_LLEXT=y CONFIG_LLEXT_STORAGE_WRITABLE=y CONFIG_LLEXT_EXPERIMENTAL=y +CONFIG_LLEXT_EDK=n CONFIG_MODULES=y # Zephyr / device drivers @@ -47,7 +54,6 @@ CONFIG_SYS_CLOCK_TICKS_PER_SEC=12000 # Zephyr / power settings CONFIG_ADSP_IMR_CONTEXT_SAVE=y -CONFIG_PM_POLICY_CUSTOM=y CONFIG_PM_PREWAKEUP_CONV_MODE_CEIL=y CONFIG_SRAM_RETENTION_MODE=n CONFIG_PM_DEVICE_RUNTIME_ASYNC=n diff --git a/app/boards/intel_adsp_ace40_nvls.conf b/app/boards/intel_adsp_ace40_nvls.conf index becef1a65f10..bb39cf026ada 100644 --- a/app/boards/intel_adsp_ace40_nvls.conf +++ b/app/boards/intel_adsp_ace40_nvls.conf @@ -12,6 +12,12 @@ CONFIG_COMP_SRC_IPC4_FULL_MATRIX=y CONFIG_COMP_TESTER=y CONFIG_FORMAT_CONVERT_HIFI3=n +# SOF / audio modules / mocks +# This mock is part of official sof-bin releases because the CI that +# tests it can't use extra CONFIGs. See #9410, #8722 and #9386 +CONFIG_COMP_GOOGLE_RTC_AUDIO_PROCESSING=m +CONFIG_GOOGLE_RTC_AUDIO_PROCESSING_MOCK=y + # SOF / infrastructure CONFIG_PROBE=y CONFIG_PROBE_DMA_MAX=2 @@ -33,6 +39,7 @@ CONFIG_HEAP_MEM_POOL_SIZE=8192 CONFIG_LLEXT=y CONFIG_LLEXT_STORAGE_WRITABLE=y CONFIG_LLEXT_EXPERIMENTAL=y +CONFIG_LLEXT_EDK=n CONFIG_MODULES=y # Zephyr / device drivers @@ -47,7 +54,6 @@ CONFIG_SYS_CLOCK_TICKS_PER_SEC=12000 # Zephyr / power settings CONFIG_ADSP_IMR_CONTEXT_SAVE=y -CONFIG_PM_POLICY_CUSTOM=y CONFIG_PM_PREWAKEUP_CONV_MODE_CEIL=y CONFIG_SRAM_RETENTION_MODE=n CONFIG_PM_DEVICE_RUNTIME_ASYNC=n diff --git a/app/boards/native_sim.conf b/app/boards/native_sim.conf new file mode 100644 index 000000000000..6df3eb89b648 --- /dev/null +++ b/app/boards/native_sim.conf @@ -0,0 +1,4 @@ +CONFIG_ZEPHYR_POSIX=y +CONFIG_SYS_HEAP_BIG_ONLY=y +CONFIG_ZEPHYR_NATIVE_DRIVERS=y +CONFIG_ZEPHYR_LOG=y diff --git a/app/boards/native_sim_libfuzzer.conf b/app/boards/native_sim_libfuzzer.conf index ece1e35845c9..ba17224424cf 100644 --- a/app/boards/native_sim_libfuzzer.conf +++ b/app/boards/native_sim_libfuzzer.conf @@ -5,6 +5,8 @@ CONFIG_ASSERT=y CONFIG_EXCEPTION_DEBUG=y CONFIG_ARCH_POSIX_TRAP_ON_FATAL=y CONFIG_SYS_HEAP_BIG_ONLY=y +CONFIG_SYS_HEAP_HARDENING_EXTREME=y +CONFIG_STACK_SENTINEL=y CONFIG_ZEPHYR_NATIVE_DRIVERS=y CONFIG_ARCH_POSIX_LIBFUZZER=y CONFIG_ZEPHYR_POSIX_FUZZ_TICKS=100 diff --git a/app/compr/README.txt b/app/compr/README.txt index a9c28957edbb..6f90c50b1524 100644 --- a/app/compr/README.txt +++ b/app/compr/README.txt @@ -16,7 +16,8 @@ cadence.conf - Base Cadence codec module only (no individual codecs) mp3.conf - MP3 decoder and encoder aac.conf - AAC decoder vorbis.conf - Vorbis decoder -all_codecs.conf - All supported codecs (MP3, AAC, Vorbis) +pcm.conf - PCM (WAV) decoder +all_codecs.conf - All supported codecs (MP3, AAC, Vorbis, PCM) Usage Examples -------------- diff --git a/app/compr/all_codecs.conf b/app/compr/all_codecs.conf index 26b507f7f452..f4aeaf3c5da5 100644 --- a/app/compr/all_codecs.conf +++ b/app/compr/all_codecs.conf @@ -1,4 +1,4 @@ -# All Cadence codecs (MP3, AAC, Vorbis) +# All Cadence codecs (MP3, AAC, Vorbis) plus SOF PCM/WAV decoder CONFIG_CADENCE_CODEC=y CONFIG_CADENCE_CODEC_MP3_DEC=y CONFIG_CADENCE_CODEC_MP3_DEC_LIB="../cadence_libs/xa_mp3_dec.a" @@ -8,4 +8,4 @@ CONFIG_CADENCE_CODEC_AAC_DEC=y CONFIG_CADENCE_CODEC_AAC_DEC_LIB="../cadence_libs/xa_aac_dec.a" CONFIG_CADENCE_CODEC_VORBIS_DEC=y CONFIG_CADENCE_CODEC_VORBIS_DEC_LIB="../cadence_libs/xa_vorbis_dec.a" - +CONFIG_SOF_COMPRESS_CODEC_PCM_DEC=y diff --git a/app/compr/pcm.conf b/app/compr/pcm.conf new file mode 100644 index 000000000000..18a0e0e2810b --- /dev/null +++ b/app/compr/pcm.conf @@ -0,0 +1,3 @@ +# PCM (wav) decoder +CONFIG_CADENCE_CODEC=y +CONFIG_SOF_COMPRESS_CODEC_PCM_DEC=y diff --git a/app/overlays/ptl/ll_userspace_overlay.conf b/app/overlays/ptl/ll_userspace_overlay.conf new file mode 100644 index 000000000000..8888a66f4c24 --- /dev/null +++ b/app/overlays/ptl/ll_userspace_overlay.conf @@ -0,0 +1,38 @@ +# This is overlay configuration file to enable user-space LL audio +# pipeline support. This overlay is intended for development +# purposes. If user-space LL is enabled for some build target by +# default, the settings should be made in the SOF board file directly. + +# Enable user-space LL support in SOF +# ----------------------------------- + +CONFIG_SOF_USERSPACE_LL=y + +# make the drivers work in user-space +CONFIG_SOF_USERSPACE_INTERFACE_DMA=y +CONFIG_DAI_USERSPACE=y + +# Temporary settings that are needed currently to enable user-space LL +# -------------------------------------------------------------------- + +# problem with DSP panics due to illegal instruction hit in user-space if cold +# store execution is enabled. Disable it for now until rootcause is found. +CONFIG_COLD_STORE_EXECUTE_DRAM=n +CONFIG_COLD_STORE_EXECUTE_DEBUG=n + +# telemetry not yet user-space compatible +CONFIG_SOF_TELEMETRY_PERFORMANCE_MEASUREMENTS=n +CONFIG_SOF_TELEMETRY_IO_PERFORMANCE_MEASUREMENTS=n + +# disable loadable modules (hits privilege issues in user-space now) +CONFIG_LLEXT_STORAGE_WRITABLE=n +CONFIG_LLEXT_EXPERIMENTAL=n +CONFIG_MODULES=n + +# some of current boot tests interfere with user-space setup +CONFIG_SOF_BOOT_TEST_ALLOWED=n + +# misc features not yet compatible with user-space +CONFIG_CROSS_CORE_STREAM=n +CONFIG_INTEL_ADSP_MIC_PRIVACY=n +CONFIG_XRUN_NOTIFICATIONS_ENABLE=n diff --git a/app/src/main.c b/app/src/main.c index 12cc3ffdc73a..aa9ee6960d8d 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -57,7 +57,7 @@ static int sof_app_main(void) return 0; } -#if CONFIG_SOF_BOOT_TEST && defined(QEMU_BOOT_TESTS) +#if defined(QEMU_BOOT_TESTS) /* cleanly exit qemu so CI can continue and check test results */ static inline void qemu_xtensa_exit(int status) { @@ -73,14 +73,31 @@ static inline void qemu_xtensa_exit(int status) } #endif +#ifdef CONFIG_REBOOT +void sys_arch_reboot(int type) +{ +#if defined(QEMU_BOOT_TESTS) + qemu_xtensa_exit(type); +#endif + while (1) { + k_cpu_idle(); + } +} +#endif + #if CONFIG_ZTEST void test_main(void) { sof_app_main(); -#if CONFIG_SOF_BOOT_TEST && defined(QEMU_BOOT_TESTS) +#if CONFIG_SOF_BOOT_TEST && (defined(QEMU_BOOT_TESTS) || CONFIG_SOF_BOOT_TEST_STANDALONE) sof_run_boot_tests(); +#if defined(QEMU_BOOT_TESTS) + /* qemu_xtensa_exit() only exists for QEMU targets; a standalone + * boot test (e.g. native_sim) just returns from test_main() + */ qemu_xtensa_exit(0); #endif +#endif } #else int main(void) diff --git a/lmdk/cmake/build.cmake b/lmdk/cmake/build.cmake index 047a085257f8..df6a2955bfc8 100644 --- a/lmdk/cmake/build.cmake +++ b/lmdk/cmake/build.cmake @@ -11,7 +11,10 @@ include(${CMAKE_CURRENT_LIST_DIR}/config.cmake) # Build common module functions from sof to a static library add_library(sof STATIC) -target_include_directories(sof PRIVATE "${SOF_BASE}/src/include") +target_include_directories(sof PRIVATE + "${SOF_BASE}/src/include" + "${LMDK_BASE}/include" +) add_subdirectory("${SOF_BASE}/src/module" module_api) foreach(MODULE ${MODULES_LIST}) diff --git a/lmdk/include/rtos/panic.h b/lmdk/include/rtos/panic.h new file mode 100644 index 000000000000..65fafab75826 --- /dev/null +++ b/lmdk/include/rtos/panic.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2026 Intel Corporation. All rights reserved. + * + * Author: Piotr Hoppe + */ + +#ifndef __LMDK_RTOS_PANIC_H__ +#define __LMDK_RTOS_PANIC_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +/* runtime assertion */ +#define assert(x) do { if (!(x)) while (1); } while (0) + +#ifdef __cplusplus +} +#endif + +#endif /* __LMDK_RTOS_PANIC_H__ */ + diff --git a/lmdk/include/sof/math/numbers.h b/lmdk/include/sof/math/numbers.h new file mode 100644 index 000000000000..36bf29fab8f2 --- /dev/null +++ b/lmdk/include/sof/math/numbers.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2016 Intel Corporation. All rights reserved. + * + * Author: Seppo Ingalsuo + * Liam Girdwood + * Keyon Jie + */ + +/* + * LMDK-local copy of . + * + * Loadable modules must not pull in the whole SOF src/include tree. This file + * mirrors the parts of the SOF header that the sink/source module API relies on + * (mainly the ROUND_UP/ROUND_DOWN/MIN/MAX helpers) so that the unchanged + * "#include " resolves inside the LMDK build without + * exposing the rest of the SOF headers. + */ + +#ifndef __SOF_MATH_NUMBERS_H__ +#define __SOF_MATH_NUMBERS_H__ + +#include + +/* Unsafe and portable macros for consistency with Zephyr. + * See SEI CERT-C PRE31-C + */ +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) < (b) ? (b) : (a)) + +#define ROUND_DOWN(size, alignment) ({ \ + __typeof__(size) __size = (size); \ + __typeof__(alignment) __alignment = (alignment); \ + __size - (__size % __alignment); \ +}) + +#define ROUND_UP(size, alignment) ({ \ + __typeof__(size) __size = (size); \ + __typeof__(alignment) __alignment = (alignment); \ + ((__size + __alignment - 1) / __alignment) * __alignment; \ +}) + +#define ABS(a) ({ \ + __typeof__(a) __a = (a); \ + __a < 0 ? -__a : __a; \ +}) +#define SGN(a) ({ \ + __typeof__(a) __a = (a); \ + __a < 0 ? -1 : \ + __a > 0 ? 1 : 0; \ +}) + +#endif /* __SOF_MATH_NUMBERS_H__ */ diff --git a/posix/include/rtos/alloc.h b/posix/include/rtos/alloc.h index 84751ef9f1f4..c599afe08d82 100644 --- a/posix/include/rtos/alloc.h +++ b/posix/include/rtos/alloc.h @@ -120,6 +120,56 @@ void sof_heap_free(struct k_heap *heap, void *addr); struct k_heap *sof_sys_heap_get(void); struct k_heap *sof_sys_user_heap_get(void); +/* Posix version of struct mod_alloc_ctx without vregion support */ +struct vregion; + +struct mod_alloc_ctx { + struct k_heap *heap; + struct vregion *vreg; +}; + +/** + * Allocate memory from a mod_alloc_ctx context. + * Dummy version, only heap allocation is supported + */ +static inline void *sof_ctx_alloc(struct mod_alloc_ctx *ctx, uint32_t flags, + size_t size, size_t alignment) +{ + return sof_heap_alloc(ctx ? ctx->heap : NULL, flags, size, alignment); +} + +/** + * Allocate zero-initialized memory from a mod_alloc_ctx context. + * @param ctx Allocation context. + * @param flags Allocation flags (SOF_MEM_FLAG_*). + * @param size Size in bytes. + * @param alignment Required alignment in bytes. + * @return Pointer to allocated memory or NULL on failure. + */ +static inline void *sof_ctx_zalloc(struct mod_alloc_ctx *ctx, uint32_t flags, + size_t size, size_t alignment) +{ + void *ptr = sof_ctx_alloc(ctx, flags, size, alignment); + + if (ptr) + memset(ptr, 0, size); + + return ptr; +} + +/** + * Free memory allocated from a mod_alloc_ctx context. + * @param ctx Allocation context. + * @param ptr Pointer to free. + */ +static inline void sof_ctx_free(struct mod_alloc_ctx *ctx, void *ptr) +{ + if (!ptr) + return; + + sof_heap_free(ctx ? ctx->heap : NULL, ptr); +} + /** * Calculates length of the null-terminated string. * @param s String. diff --git a/posix/include/rtos/mutex.h b/posix/include/rtos/mutex.h index 19b360bdaea5..3bd01342ced5 100644 --- a/posix/include/rtos/mutex.h +++ b/posix/include/rtos/mutex.h @@ -62,4 +62,25 @@ static inline int sys_mutex_unlock(struct sys_mutex *mutex) return 0; } +/* provide a no-op implementation for zephyr/sys/sem.h */ + +struct sys_sem { +}; + +static inline int sys_sem_init(struct sys_sem *sem, unsigned int initial_count, + unsigned int limit) +{ + return 0; +} + +static inline int sys_sem_give(struct sys_sem *sem) +{ + return 0; +} + +static inline int sys_sem_take(struct sys_sem *sem, k_timeout_t timeout) +{ + return 0; +} + #endif diff --git a/posix/include/rtos/task.h b/posix/include/rtos/task.h index 5aca574a5787..bb09ac63bac4 100644 --- a/posix/include/rtos/task.h +++ b/posix/include/rtos/task.h @@ -18,6 +18,7 @@ struct comp_dev; struct sof; +struct schedule_data; /** \brief Predefined LL task priorities. */ #define SOF_TASK_PRI_HIGH 0 /* priority level 0 - high */ @@ -60,6 +61,7 @@ struct task { uint16_t priority; /**< priority of the task (used by LL) */ uint16_t core; /**< execution core */ uint16_t flags; /**< custom flags */ + struct schedule_data *sch; /**< scheduler bound to task */ enum task_state state; /**< current state */ void *data; /**< custom data passed to all ops */ struct list_item list; /**< used by schedulers to hold tasks */ diff --git a/scripts/build-alsa-tools.sh b/scripts/build-alsa-tools.sh index 5e1e7f75d39d..01cdb761ee1b 100755 --- a/scripts/build-alsa-tools.sh +++ b/scripts/build-alsa-tools.sh @@ -16,8 +16,8 @@ declare -a REPOS=( # the repository if this commit ID is not found. Leave empty to skip. # This array order must align with REPO array above. declare -a COMMIT_ID=( - "df8f1cc1ec9d9ee15be5e2c23ad25b9389fd8766" - "09550cd393b1a7d307ee6f26637b1ed7bd275e38" + "08b532cd3da9ac8f683bcb4e4beb9b74c39c1782" + "9feb22ba45b48729729c8d194aaf1bc082a6842a" # Add more IDs here... ) diff --git a/scripts/sof-crash-decode.py b/scripts/sof-crash-decode.py index 8f76921431be..2db0222f620d 100755 --- a/scripts/sof-crash-decode.py +++ b/scripts/sof-crash-decode.py @@ -35,6 +35,76 @@ import os import json import shlex +import ast +import operator + + +# Largest shift we accept: linker addresses/sizes fit well under 2**64, so a +# bigger shift is nonsensical and only serves to build a huge integer. +_MAX_SHIFT = 64 +# Reject any literal or intermediate result beyond 64 bits, and cap the source +# length, so a crafted expression can't build a huge integer or stall the parse. +_MAX_VALUE = 1 << 64 +_MAX_EXPR_LEN = 256 + + +def _trunc_div(a, b): + # truncate toward zero (C semantics); Python's // floors instead + q = abs(a) // abs(b) + return -q if (a < 0) != (b < 0) else q + + +def _lshift(a, b): + # reject absurd shift counts to avoid building a massive integer + if b < 0 or b > _MAX_SHIFT: + raise ValueError("shift count out of range") + return a << b + + +# Operators allowed when evaluating arithmetic from an untrusted linker script. +_SAFE_OPS = { + ast.Add: operator.add, ast.Sub: operator.sub, + ast.Mult: operator.mul, ast.Div: _trunc_div, + ast.Mod: operator.mod, ast.LShift: _lshift, + ast.RShift: operator.rshift, ast.BitOr: operator.or_, + ast.BitAnd: operator.and_, ast.BitXor: operator.xor, + ast.USub: operator.neg, ast.UAdd: operator.pos, +} + + +def safe_eval_int(expr): + """Evaluate an integer arithmetic expression without executing code. + + A crash bundle's linker.cmd is attacker-controllable, so its MEMORY + expressions must never be passed to eval(). Only integer literals and + basic arithmetic operators are accepted, and values are bounded to 64 + bits. Anything unsupported or out of range raises an exception + (ValueError, or ZeroDivisionError on '/ 0'); the caller treats any such + failure as 'skip this entry'. + """ + def _check(value): + if abs(value) >= _MAX_VALUE: + raise ValueError("value out of range") + return value + + def _eval(node): + if isinstance(node, ast.Expression): + return _eval(node.body) + if isinstance(node, ast.Constant): + # reject bool (a subclass of int) and everything non-integer + if type(node.value) is int: + return _check(node.value) + raise ValueError("non-integer constant") + if isinstance(node, ast.BinOp) and type(node.op) in _SAFE_OPS: + return _check(_SAFE_OPS[type(node.op)](_eval(node.left), + _eval(node.right))) + if isinstance(node, ast.UnaryOp) and type(node.op) in _SAFE_OPS: + return _check(_SAFE_OPS[type(node.op)](_eval(node.operand))) + raise ValueError("unsupported expression") + + if len(expr) > _MAX_EXPR_LEN: + raise ValueError("expression too long") + return _eval(ast.parse(expr, mode='eval')) XTENSA_EXCCAUSE = { 0: "No Error (or IllegalInstruction)", @@ -151,8 +221,8 @@ def parse_linker_cmd(filepath): org_expr = m_org.group(1).strip() len_expr = m_len.group(1).strip() try: - org_val = eval(org_expr) - len_val = eval(len_expr) + org_val = safe_eval_int(org_expr) + len_val = safe_eval_int(len_expr) # Ignore debug regions if not (name.startswith('.debug') or name.startswith('.stab')): regions.append({'name': name, 'start': org_val, 'end': org_val + len_val}) diff --git a/scripts/sof-qemu-run.py b/scripts/sof-qemu-run.py index fc985e254d51..4fc40ac439e7 100755 --- a/scripts/sof-qemu-run.py +++ b/scripts/sof-qemu-run.py @@ -16,6 +16,8 @@ import subprocess import argparse import os +import pathlib +import shutil import re @@ -76,22 +78,72 @@ def main(): parser = argparse.ArgumentParser(description="Run QEMU via west and automatically decode crashes.") parser.add_argument("--build-dir", default="build", help="Path to the build directory containing zephyr.elf, linker.cmd, etc. Defaults to 'build'.") parser.add_argument("--log-file", default="qemu-run.log", help="Path to save the QEMU output log. Defaults to 'qemu-run.log'.") + parser.add_argument("--valgrind", action="store_true", help="Run the executable under Valgrind (only valid for native_sim).") args = parser.parse_args() - # Make absolute path just in case + # Resolve to an absolute path; args.build_dir may be passed relative to + # the caller's working directory. build_dir = os.path.abspath(args.build_dir) print(f"Starting QEMU test runner. Monitoring for crashes (Build Dir: {args.build_dir})...") # We will use pexpect to spawn the west command to get PTY features - import shutil west_path = shutil.which("west") if not west_path: print("[sof-qemu-run] Error: 'west' command not found in PATH.") print("Please ensure you have sourced the Zephyr environment (e.g., source zephyr-env.sh).") sys.exit(1) - child = pexpect.spawn(west_path, ["-v", "build", "-t", "run"], encoding='utf-8') + # Detect the board configuration using zcmake (same approach as xtensa-build-zephyr.py) + is_native_sim = False + try: + # zcmake lives in the Zephyr tree, so its location is added to sys.path + # at runtime (same approach as xtensa-build-zephyr.py) and the import is + # kept local and guarded so a missing Zephyr tree degrades gracefully. + sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), + '..', '..', 'zephyr', 'scripts', 'west_commands')) + import zcmake + cmake_cache = zcmake.CMakeCache.from_build_dir(pathlib.Path(build_dir)) + board = cmake_cache.get("CACHED_BOARD") or cmake_cache.get("BOARD") or "" + if "native_sim" in board: + is_native_sim = True + except Exception: + pass + + # Determine execution command + # If the user is running the python script directly from outside the workspace, we need to provide the source directory. + # But if west finds it automatically (or we are in the build dir), providing `-s` might clear the CACHED_BOARD config. + run_cmd = [west_path, "-v", "build", "-d", build_dir] + + # Check if we are physically sitting inside the build directory + if os.path.abspath(".") != os.path.abspath(build_dir): + # We need to explicitly supply the app source to prevent west from crashing + app_source_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "app")) + run_cmd.extend(["-s", app_source_dir]) + + run_cmd.extend(["-t", "run"]) + + if args.valgrind: + if not is_native_sim: + print("[sof-qemu-run] Error: --valgrind is only supported for the native_sim board.") + sys.exit(1) + + print("[sof-qemu-run] Rebuilding before valgrind...") + subprocess.run([west_path, "build", "-d", build_dir], check=True) + + valgrind_path = shutil.which("valgrind") + if not valgrind_path: + print("[sof-qemu-run] Error: 'valgrind' command not found in PATH.") + sys.exit(1) + + exe_path = os.path.join(build_dir, "zephyr", "zephyr.exe") + if not os.path.isfile(exe_path): + print(f"[sof-qemu-run] Error: Executable not found at {exe_path}") + sys.exit(1) + + run_cmd = [valgrind_path, exe_path] + + child = pexpect.spawn(run_cmd[0], run_cmd[1:], encoding='utf-8') # We will accumulate output to check for crashes full_output = "" @@ -157,36 +209,39 @@ def main(): run_sof_crash_decode(build_dir, full_output) else: - print("\n[sof-qemu-run] No crash detected. Interacting with QEMU Monitor to grab registers...") - - # We need to send Ctrl-A c to enter the monitor - if child.isalive(): - child.send("\x01c") # Ctrl-A c - try: - # Wait for (qemu) prompt - child.expect(r"\(qemu\)", timeout=5) - # Send "info registers" - child.sendline("info registers") - # Wait for the next prompt - child.expect(r"\(qemu\)", timeout=5) - - info_regs_output = child.before - print("\n[sof-qemu-run] Successfully extracted registers from QEMU monitor.\n") - - # Quit qemu safely - child.sendline("quit") - child.expect(pexpect.EOF, timeout=2) - child.close() - - # Run the decoder on the intercepted register output - run_sof_crash_decode(build_dir, info_regs_output) - except pexpect.TIMEOUT: - print("\n[sof-qemu-run] Timed out waiting for QEMU monitor. Is it running?") - child.close(force=True) - except pexpect.EOF: - print("\n[sof-qemu-run] QEMU terminated before we could run monitor commands.") + if is_native_sim: + print("\n[sof-qemu-run] No crash detected. (Skipping QEMU monitor interaction for native_sim)") else: - print("\n[sof-qemu-run] Process is no longer alive, cannot extract registers.") + print("\n[sof-qemu-run] No crash detected. Interacting with QEMU Monitor to grab registers...") + + # We need to send Ctrl-A c to enter the monitor + if child.isalive(): + child.send("\x01c") # Ctrl-A c + try: + # Wait for (qemu) prompt + child.expect(r"\(qemu\)", timeout=5) + # Send "info registers" + child.sendline("info registers") + # Wait for the next prompt + child.expect(r"\(qemu\)", timeout=5) + + info_regs_output = child.before + print("\n[sof-qemu-run] Successfully extracted registers from QEMU monitor.\n") + + # Quit qemu safely + child.sendline("quit") + child.expect(pexpect.EOF, timeout=2) + child.close() + + # Run the decoder on the intercepted register output + run_sof_crash_decode(build_dir, info_regs_output) + except pexpect.TIMEOUT: + print("\n[sof-qemu-run] Timed out waiting for QEMU monitor. Is it running?") + child.close(force=True) + except pexpect.EOF: + print("\n[sof-qemu-run] QEMU terminated before we could run monitor commands.") + else: + print("\n[sof-qemu-run] Process is no longer alive, cannot extract registers.") if __name__ == "__main__": main() diff --git a/scripts/sof-qemu-run.sh b/scripts/sof-qemu-run.sh index e1ece1dd5125..ef915fbb1318 100755 --- a/scripts/sof-qemu-run.sh +++ b/scripts/sof-qemu-run.sh @@ -2,17 +2,54 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright(c) 2026 Intel Corporation. All rights reserved. -# Define the build directory from the first argument (or default) -BUILD_DIR="${1:-build}" +# Get the directory of this script +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +SOF_WORKSPACE="$(dirname "$(dirname "$SCRIPT_DIR")")" + +TARGET="native_sim" +VALGRIND_ARG="" + +usage() { + cat <' relative + to the workspace. (default: native_sim) +EOF + exit 0 +} + +while [[ $# -gt 0 ]]; do + case $1 in + -h|--help) + usage + ;; + --valgrind) + VALGRIND_ARG="--valgrind" + ;; + *) + # Allow users who pass the directory name directly out of habit to still work + if [[ "$1" == build-* ]]; then + TARGET="${1#build-}" + else + TARGET="$1" + fi + ;; + esac + shift +done + +BUILD_DIR="${SOF_WORKSPACE}/build-${TARGET}" # Find and source the zephyr environment script, typically via the sof-venv wrapper # or directly if running in a known zephyrproject layout. # We will use the existing helper sof-venv.sh to get the right environment. -# Get the directory of this script -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -SOF_WORKSPACE="$(dirname "$(dirname "$SCRIPT_DIR")")" - # Use the SOF workspace to locate the virtual environment VENV_DIR="$SOF_WORKSPACE/.venv" echo "Using SOF environment at $SOF_WORKSPACE" @@ -20,9 +57,5 @@ echo "Using SOF environment at $SOF_WORKSPACE" # start the virtual environment source ${VENV_DIR}/bin/activate -# Execute the QEMU runner from within the correct build directory -cd "${BUILD_DIR}" || exit 1 - # Finally run the python script which will now correctly inherit 'west' from the sourced environment. -python3 "${SCRIPT_DIR}/sof-qemu-run.py" --build-dir "${BUILD_DIR}" - +python3 "${SCRIPT_DIR}/sof-qemu-run.py" --build-dir "${BUILD_DIR}" $VALGRIND_ARG diff --git a/scripts/xtensa-build-zephyr.py b/scripts/xtensa-build-zephyr.py index a3f6581999b9..f20c95f7262a 100755 --- a/scripts/xtensa-build-zephyr.py +++ b/scripts/xtensa-build-zephyr.py @@ -251,6 +251,10 @@ class PlatformConfig: "zephyr", "qemu_xtensa/dc233c/mmu", "", "", "zephyr" ), + "native_sim" : PlatformConfig( + "zephyr", "native_sim", + "", "", "zephyr" + ), } platform_configs = platform_configs_all.copy() @@ -1194,8 +1198,8 @@ def install_platform(platform, sof_output_dir, platf_build_environ, platform_wco install_key_dir = install_key_dir / args.key_type_subdir os.makedirs(install_key_dir, exist_ok=True) - # looses file owner and group - file is commonly accessible, dont install qemu. - if platform not in ("qemu_xtensa", "qemu_xtensa_mmu"): + # loses file owner and group - file is commonly accessible, don't install for qemu. + if platform not in ("qemu_xtensa", "qemu_xtensa_mmu", "native_sim"): shutil.copy2(abs_build_dir / "zephyr.ri", install_key_dir / output_fwname) if args.deployable_build and platform_configs[platform].ipc4: @@ -1349,7 +1353,7 @@ def gzip_compress(fname, gzdst=None): RI_INFO_UNSUPPORTED += ['imx8', 'imx8x', 'imx8m', 'imx8m_cm7', 'imx8ulp', 'imx95'] RI_INFO_UNSUPPORTED += ['rn', 'acp_6_0', 'acp_7_0', 'acp_7_x'] RI_INFO_UNSUPPORTED += ['mt8186', 'mt8188', 'mt8195', 'mt8196', 'mt8365'] -RI_INFO_UNSUPPORTED += ['qemu_xtensa', 'qemu_xtensa_mmu'] +RI_INFO_UNSUPPORTED += ['qemu_xtensa', 'qemu_xtensa_mmu', 'native_sim'] # For temporary workarounds. Unlike _UNSUPPORTED above, the platforms below will print a warning. diff --git a/smex/elf.c b/smex/elf.c index eb0994530c6b..6069f12f6b92 100644 --- a/smex/elf.c +++ b/smex/elf.c @@ -44,8 +44,29 @@ static int elf_read_sections(struct elf_module *module, bool verbose) return count > 0 ? -ENODATA : -errno; } - /* read in strings */ - module->strings = calloc(1, section[hdr->shstrndx].size); + /* the string-table section index comes from the ELF header and is used + * to index section[]; reject an out-of-range value before dereferencing + */ + if (hdr->shstrndx >= hdr->shnum) { + fprintf(stderr, "error: %s invalid shstrndx %u >= shnum %u\n", + module->elf_file, hdr->shstrndx, hdr->shnum); + return -ENOEXEC; + } + + /* a zero-size string section leaves module->strings unusable and would + * break later string lookups; reject it explicitly + */ + if (section[hdr->shstrndx].size == 0) { + fprintf(stderr, "error: %s has zero-size string section\n", + module->elf_file); + return -ENOEXEC; + } + + /* read in strings; allocate one extra byte (calloc zeroes it) so the + * table is always NUL-terminated and string lookups cannot run off the + * end even if the section itself lacks a terminator + */ + module->strings = calloc(1, section[hdr->shstrndx].size + 1); if (!module->strings) { fprintf(stderr, "error: failed %s to read ELF strings for %d\n", module->elf_file, -errno); @@ -67,6 +88,18 @@ static int elf_read_sections(struct elf_module *module, bool verbose) return count > 0 ? -ENODATA : -errno; } + /* every section name is used as an offset into the string table; make + * sure each stays within it so later "module->strings + name" reads + * cannot run past the table + */ + for (i = 0; i < hdr->shnum; i++) { + if (section[i].name >= section[hdr->shstrndx].size) { + fprintf(stderr, "error: %s section %d name offset %u out of range\n", + module->elf_file, i, section[i].name); + return -ENOEXEC; + } + } + module->bss_index = elf_find_section(module, ".bss"); if (module->bss_index < 0) { fprintf(stderr, "Can't find .bss section in %s", @@ -392,8 +425,22 @@ int elf_find_section(const struct elf_module *module, const char *name) return -EINVAL; } + if (hdr->shstrndx >= hdr->shnum) { + fprintf(stderr, "error: invalid shstrndx %u >= shnum %u\n", + hdr->shstrndx, hdr->shnum); + return -EINVAL; + } + section = &module->section[hdr->shstrndx]; + /* a zero-size string section would make the buffer[size - 1] + * NUL-termination below write before the allocation + */ + if (section->size == 0) { + fprintf(stderr, "error: zero-size string section\n"); + return -EINVAL; + } + /* alloc data data */ buffer = calloc(1, section->size); if (!buffer) @@ -482,6 +529,10 @@ int elf_read_section(const struct elf_module *module, const char *section_name, error: free(*dst_buff); + /* clear the caller's pointer so a caller cleanup path (e.g. ldc.c's + * "if (buffer) free(buffer)") does not free the same buffer again + */ + *dst_buff = NULL; return ret; } diff --git a/smex/ldc.c b/smex/ldc.c index 4eccdb1c0918..a44b67a05973 100644 --- a/smex/ldc.c +++ b/smex/ldc.c @@ -26,6 +26,13 @@ static int fw_version_copy(const struct elf_module *src, if (section_size < 0) return section_size; + if ((size_t)section_size < sizeof(struct sof_ipc_fw_ready)) { + fprintf(stderr, "error: .fw_ready section too small: %d\n", + section_size); + free(buffer); + return -EINVAL; + } + memcpy(&header->version, &((struct sof_ipc_fw_ready *)buffer)->version, sizeof(header->version)); @@ -50,13 +57,37 @@ static int fw_version_copy(const struct elf_module *src, return section_size; ext_hdr = (struct ext_man_elem_header *)buffer; - while ((uintptr_t)ext_hdr < (uintptr_t)buffer + section_size) { + while ((uintptr_t)ext_hdr + sizeof(*ext_hdr) <= + (uintptr_t)buffer + section_size) { if (ext_hdr->type == EXT_MAN_ELEM_DBG_ABI) { + /* make sure the whole dbg-abi element is within the + * section before reading it + */ + if (ext_hdr->elem_size < sizeof(struct ext_man_dbg_abi) || + (uintptr_t)ext_hdr + sizeof(struct ext_man_dbg_abi) > + (uintptr_t)buffer + section_size) { + fprintf(stderr, "error: %s truncated dbg-abi element\n", + src->elf_file); + free(buffer); + return -ENOEXEC; + } header->version.abi_version = ((struct ext_man_dbg_abi *) ext_hdr)->dbg_abi.abi_dbg_version; break; } + /* a malformed element size would loop forever (0) or advance + * the cursor past the section; reject the image rather than + * silently stopping + */ + if (ext_hdr->elem_size == 0 || + (uintptr_t)ext_hdr + ext_hdr->elem_size > + (uintptr_t)buffer + section_size) { + fprintf(stderr, "error: %s malformed ext-manifest element\n", + src->elf_file); + free(buffer); + return -ENOEXEC; + } //move to the next entry ext_hdr = (struct ext_man_elem_header *) ((uint8_t *)ext_hdr + ext_hdr->elem_size); diff --git a/src/arch/host/configs/library_defconfig b/src/arch/host/configs/library_defconfig index 28c486bec58d..1279dd26c924 100644 --- a/src/arch/host/configs/library_defconfig +++ b/src/arch/host/configs/library_defconfig @@ -14,6 +14,7 @@ CONFIG_COMP_MFCC=y CONFIG_COMP_MODULE_ADAPTER=y CONFIG_COMP_MULTIBAND_DRC=y CONFIG_COMP_MUX=y +CONFIG_COMP_PHASE_VOCODER=y CONFIG_COMP_RTNR=y CONFIG_COMP_SEL=y CONFIG_COMP_SOUND_DOSE=y diff --git a/src/audio/CMakeLists.txt b/src/audio/CMakeLists.txt index 29e602871af7..92002c8b7c1c 100644 --- a/src/audio/CMakeLists.txt +++ b/src/audio/CMakeLists.txt @@ -71,6 +71,9 @@ if(NOT CONFIG_COMP_MODULE_SHARED_LIBRARY_BUILD) if(CONFIG_COMP_MUX) add_subdirectory(mux) endif() + if(CONFIG_COMP_PHASE_VOCODER) + add_subdirectory(phase_vocoder) + endif() if(CONFIG_COMP_RTNR) add_subdirectory(rtnr) endif() @@ -126,6 +129,9 @@ if(NOT CONFIG_COMP_MODULE_SHARED_LIBRARY_BUILD) if(CONFIG_COMP_TONE) add_subdirectory(tone) endif() + if(CONFIG_UAOL_INTEL_ADSP) + add_local_sources(sof intel_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/Kconfig b/src/audio/Kconfig index 1f7d362ffdc2..8accb25738a2 100644 --- a/src/audio/Kconfig +++ b/src/audio/Kconfig @@ -42,6 +42,15 @@ config HOST_DMA_STREAM_SYNCHRONIZATION for each group, different than the default one determined by the system tick frequency. This feature will allow host lower power consumption in scenarios with deep buffering. +config HOST_DMA_IPC_POSITION_UPDATES + bool "Support for stream position updates via IPC messages" + depends on IPC_MAJOR_3 + default y + help + Support firmware functionality to report stream position updates + by sending an IPC message whenever one period of audio is transferred. + Most platforms provide more efficient ways to query the DMA status. + config COMP_CHAIN_DMA bool "Chain DMA component" depends on IPC_MAJOR_4 @@ -139,6 +148,7 @@ rsource "module_adapter/Kconfig" rsource "multiband_drc/Kconfig" rsource "mux/Kconfig" rsource "nxp/Kconfig" +rsource "phase_vocoder/Kconfig" rsource "rtnr/Kconfig" rsource "selector/Kconfig" rsource "smart_amp/Kconfig" diff --git a/src/audio/aria/aria.c b/src/audio/aria/aria.c index dc265cfac240..ef7285d2f564 100644 --- a/src/audio/aria/aria.c +++ b/src/audio/aria/aria.c @@ -123,6 +123,13 @@ static int aria_init(struct processing_module *mod) list_init(&dev->bsource_list); list_init(&dev->bsink_list); + /* sample group size is used as a divisor below, reject configs that make it zero */ + if (!base_cfg->audio_fmt.channels_count || base_cfg->audio_fmt.depth < 8) { + comp_err(dev, "invalid channels:%u depth:%d", + base_cfg->audio_fmt.channels_count, base_cfg->audio_fmt.depth); + return -EINVAL; + } + cd = mod_zalloc(mod, sizeof(*cd)); if (!cd) { return -ENOMEM; diff --git a/src/audio/base_fw.c b/src/audio/base_fw.c index b86db469765a..2602add04cdc 100644 --- a/src/audio/base_fw.c +++ b/src/audio/base_fw.c @@ -100,6 +100,14 @@ static void get_codec_info(struct sof_tlv **tuple) codec_info.items[codec_info.count++] = SET_CODEC_INFO_ITEM(SND_AUDIOCODEC_VORBIS, SOF_IPC_STREAM_PLAYBACK); #endif +#ifdef CONFIG_COMP_MFCC + codec_info.items[codec_info.count++] = + SET_CODEC_INFO_ITEM(SND_AUDIOCODEC_BESPOKE, SOF_IPC_STREAM_CAPTURE); +#endif +#ifdef CONFIG_SOF_COMPRESS_CODEC_PCM_DEC + codec_info.items[codec_info.count++] = + SET_CODEC_INFO_ITEM(SND_AUDIOCODEC_PCM, SOF_IPC_STREAM_PLAYBACK); +#endif if (!codec_info.count) return; @@ -277,6 +285,9 @@ __cold static uint32_t basefw_set_system_time(uint32_t param_id, bool first_bloc if (!(first_block && last_block)) return IPC4_INVALID_REQUEST; + if (data_offset < sizeof(struct ipc4_system_time)) + return IPC4_ERROR_INVALID_PARAM; + global_system_time_info.host_time.val_l = ((const struct ipc4_system_time *)data)->val_l; global_system_time_info.host_time.val_u = ((const struct ipc4_system_time *)data)->val_u; @@ -341,6 +352,12 @@ __cold static int basefw_kcps_allocation_request(struct ipc4_resource_kcps *requ assert_can_be_cold(); #if CONFIG_KCPS_DYNAMIC_CLOCK_CONTROL + if (request->core_id >= CONFIG_CORE_COUNT) { + tr_err(&ipc_tr, "invalid core_id %u", + request->core_id); + return IPC4_ERROR_INVALID_PARAM; + } + if (core_kcps_adjust(request->core_id, request->kcps)) return IPC4_ERROR_INVALID_PARAM; #endif @@ -770,6 +787,13 @@ __cold static int basefw_dma_control(bool first_block, bool last_block, uint32_t } dma_control = (struct ipc4_dma_control *)data; + + /* data_offset must cover the fixed header before computing the payload size */ + if (data_offset < sizeof(struct ipc4_dma_control)) { + tr_err(&ipc_tr, "DMA Control message too short: %u", data_offset); + return IPC4_ERROR_INVALID_PARAM; + } + data_size = data_offset - sizeof(struct ipc4_dma_control); if (data_size < (dma_control->config_length * sizeof(uint32_t))) { @@ -839,8 +863,7 @@ static SHARED_DATA struct comp_driver_info comp_basefw_info = { UT_STATIC void sys_comp_basefw_init(void) { - comp_register(platform_shared_get(&comp_basefw_info, - sizeof(comp_basefw_info))); + comp_register(&comp_basefw_info); } DECLARE_MODULE(sys_comp_basefw_init); diff --git a/src/audio/base_fw_intel.c b/src/audio/base_fw_intel.c index 67b87a0afed4..bcb9fca8e1f2 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/buffers/comp_buffer.c b/src/audio/buffers/comp_buffer.c index 502103693689..8a3d44133d4b 100644 --- a/src/audio/buffers/comp_buffer.c +++ b/src/audio/buffers/comp_buffer.c @@ -17,7 +17,6 @@ #include #include #include -#include #include #include #include @@ -157,12 +156,12 @@ static void comp_buffer_free(struct sof_audio_buffer *audio_buffer) struct mod_alloc_ctx *alloc = buffer->audio_buffer.alloc; -#ifdef CONFIG_SOF_USERSPACE_LL - assert(alloc); - sof_ctx_free(alloc, buffer->stream.addr); -#else - rfree(buffer->stream.addr); -#endif + assert(!IS_ENABLED(CONFIG_SOF_USERSPACE_LL) || alloc); + + if (alloc) + sof_ctx_free(alloc, buffer->stream.addr); + else + sof_heap_free(sof_sys_user_heap_get(), buffer->stream.addr); if (alloc && alloc->vreg) { vregion_free(alloc->vreg, buffer); @@ -256,12 +255,14 @@ struct comp_buffer *buffer_alloc(struct mod_alloc_ctx *alloc, size_t size, uint3 return NULL; } -#ifdef CONFIG_SOF_USERSPACE_LL - assert(alloc); - stream_addr = sof_ctx_alloc(alloc, flags, size, align); -#else - stream_addr = rballoc_align(flags, size, align); -#endif + assert(!IS_ENABLED(CONFIG_SOF_USERSPACE_LL) || alloc); + + if (alloc) + stream_addr = sof_ctx_alloc(alloc, flags, size, align); + else + stream_addr = sof_heap_alloc(sof_sys_user_heap_get(), + flags | SOF_MEM_FLAG_LARGE_BUFFER, size, align); + if (!stream_addr) { tr_err(&buffer_tr, "could not alloc size = %zu bytes of flags = 0x%x", size, flags); @@ -271,12 +272,11 @@ struct comp_buffer *buffer_alloc(struct mod_alloc_ctx *alloc, size_t size, uint3 buffer = buffer_alloc_struct(alloc, stream_addr, size, flags, is_shared); if (!buffer) { tr_err(&buffer_tr, "could not alloc buffer structure"); -#ifdef CONFIG_SOF_USERSPACE_LL - assert(alloc); - sof_ctx_free(alloc, stream_addr); -#else - rfree(stream_addr); -#endif + + if (alloc) + sof_ctx_free(alloc, stream_addr); + else + sof_heap_free(sof_sys_user_heap_get(), stream_addr); } return buffer; @@ -303,13 +303,16 @@ struct comp_buffer *buffer_alloc_range(struct mod_alloc_ctx *alloc, size_t prefe if (preferred_size % minimum_size) preferred_size += minimum_size - preferred_size % minimum_size; + assert(!IS_ENABLED(CONFIG_SOF_USERSPACE_LL) || alloc); + for (size = preferred_size; size >= minimum_size; size -= minimum_size) { -#ifdef CONFIG_SOF_USERSPACE_LL - assert(alloc); - stream_addr = sof_ctx_alloc(alloc, flags, size, align); -#else - stream_addr = rballoc_align(flags, size, align); -#endif + if (alloc) + stream_addr = sof_ctx_alloc(alloc, flags, size, align); + else + stream_addr = sof_heap_alloc(sof_sys_user_heap_get(), + flags | SOF_MEM_FLAG_LARGE_BUFFER, size, + align); + if (stream_addr) break; } @@ -325,12 +328,11 @@ struct comp_buffer *buffer_alloc_range(struct mod_alloc_ctx *alloc, size_t prefe buffer = buffer_alloc_struct(alloc, stream_addr, size, flags, is_shared); if (!buffer) { tr_err(&buffer_tr, "could not alloc buffer structure"); -#ifdef CONFIG_SOF_USERSPACE_LL - assert(alloc); - sof_ctx_free(alloc, stream_addr); -#else - rfree(stream_addr); -#endif + + if (alloc) + sof_ctx_free(alloc, stream_addr); + else + sof_heap_free(sof_sys_user_heap_get(), stream_addr); } return buffer; @@ -351,9 +353,7 @@ void buffer_zero(struct comp_buffer *buffer) int buffer_set_size(struct comp_buffer *buffer, uint32_t size, uint32_t alignment) { void *new_ptr = NULL; -#ifdef CONFIG_SOF_USERSPACE_LL struct mod_alloc_ctx *alloc = buffer->audio_buffer.alloc; -#endif CORE_CHECK_STRUCT(&buffer->audio_buffer); @@ -366,12 +366,14 @@ int buffer_set_size(struct comp_buffer *buffer, uint32_t size, uint32_t alignmen if (size == audio_stream_get_size(&buffer->stream)) return 0; -#ifdef CONFIG_SOF_USERSPACE_LL - assert(alloc); - new_ptr = sof_ctx_alloc(alloc, buffer->flags, size, alignment); -#else - new_ptr = rballoc_align(buffer->flags, size, alignment); -#endif + assert(!IS_ENABLED(CONFIG_SOF_USERSPACE_LL) || alloc); + + if (alloc) + new_ptr = sof_ctx_alloc(alloc, buffer->flags, size, alignment); + else + new_ptr = sof_heap_alloc(sof_sys_user_heap_get(), + buffer->flags | SOF_MEM_FLAG_LARGE_BUFFER, size, + alignment); /* we couldn't allocate bigger chunk */ if (!new_ptr && size > audio_stream_get_size(&buffer->stream)) { @@ -382,12 +384,11 @@ int buffer_set_size(struct comp_buffer *buffer, uint32_t size, uint32_t alignmen /* use bigger chunk, else just use the old chunk but set smaller */ if (new_ptr) { -#ifdef CONFIG_SOF_USERSPACE_LL - assert(alloc); - sof_ctx_free(alloc, audio_stream_get_addr(&buffer->stream)); -#else - rfree(audio_stream_get_addr(&buffer->stream)); -#endif + if (alloc) + sof_ctx_free(alloc, audio_stream_get_addr(&buffer->stream)); + else + sof_heap_free(sof_sys_user_heap_get(), audio_stream_get_addr(&buffer->stream)); + audio_stream_set_addr(&buffer->stream, new_ptr); } @@ -402,9 +403,7 @@ int buffer_set_size_range(struct comp_buffer *buffer, size_t preferred_size, siz const size_t actual_size = audio_stream_get_size(&buffer->stream); void *new_ptr = NULL; size_t new_size; -#ifdef CONFIG_SOF_USERSPACE_LL struct mod_alloc_ctx *alloc = buffer->audio_buffer.alloc; -#endif CORE_CHECK_STRUCT(&buffer->audio_buffer); @@ -422,14 +421,17 @@ int buffer_set_size_range(struct comp_buffer *buffer, size_t preferred_size, siz if (preferred_size == actual_size) return 0; + assert(!IS_ENABLED(CONFIG_SOF_USERSPACE_LL) || alloc); + for (new_size = preferred_size; new_size >= minimum_size; new_size -= minimum_size) { -#ifdef CONFIG_SOF_USERSPACE_LL - assert(alloc); - new_ptr = sof_ctx_alloc(alloc, buffer->flags, new_size, alignment); -#else - new_ptr = rballoc_align(buffer->flags, new_size, alignment); -#endif + if (alloc) + new_ptr = sof_ctx_alloc(alloc, buffer->flags, new_size, alignment); + else + new_ptr = sof_heap_alloc(sof_sys_user_heap_get(), + buffer->flags | SOF_MEM_FLAG_LARGE_BUFFER, + new_size, alignment); + if (new_ptr) break; } @@ -443,12 +445,11 @@ int buffer_set_size_range(struct comp_buffer *buffer, size_t preferred_size, siz /* use bigger chunk, else just use the old chunk but set smaller */ if (new_ptr) { -#ifdef CONFIG_SOF_USERSPACE_LL - assert(alloc); - sof_ctx_free(alloc, audio_stream_get_addr(&buffer->stream)); -#else - rfree(audio_stream_get_addr(&buffer->stream)); -#endif + if (alloc) + sof_ctx_free(alloc, audio_stream_get_addr(&buffer->stream)); + else + sof_heap_free(sof_sys_user_heap_get(), audio_stream_get_addr(&buffer->stream)); + audio_stream_set_addr(&buffer->stream, new_ptr); } @@ -604,12 +605,20 @@ static inline struct list_item *buffer_comp_list(struct comp_buffer *buffer, * from racing attach / detach calls, but the scheduler can interrupt the IPC * thread and begin using the buffer for streaming. FIXME: this is still a * problem with different cores. + * + * Returns -EALREADY if the buffer is already attached in this direction. + * Attaching the same buffer twice would corrupt the list (list_item_prepend + * on an already-linked node creates a self-loop), so callers must propagate + * the error. */ -void buffer_attach(struct comp_buffer *buffer, struct list_item *head, int dir) +int buffer_attach(struct comp_buffer *buffer, struct list_item *head, int dir) { struct list_item *list = buffer_comp_list(buffer, dir); CORE_CHECK_STRUCT(&buffer->audio_buffer); + if (!list_is_empty(list)) + return -EALREADY; list_item_prepend(list, head); + return 0; } /* diff --git a/src/audio/buffers/ring_buffer.c b/src/audio/buffers/ring_buffer.c index 62ac121acda4..51245e91ca40 100644 --- a/src/audio/buffers/ring_buffer.c +++ b/src/audio/buffers/ring_buffer.c @@ -7,7 +7,6 @@ #include #include #include -#include #include #include diff --git a/src/audio/chain_dma.c b/src/audio/chain_dma.c index 15929dd6941c..e7207e636ee8 100644 --- a/src/audio/chain_dma.c +++ b/src/audio/chain_dma.c @@ -79,6 +79,12 @@ static int chain_host_start(struct comp_dev *dev) struct chain_dma_data *cd = comp_get_drvdata(dev); int err; + if (!cd->chan_host || !cd->chan_host->dma) { + comp_err(dev, "incomplete initialization detected, aborting host %p", + cd->chan_host); + return -ENODEV; + } + err = dma_start(cd->chan_host->dma->z_dev, cd->chan_host->index); if (err < 0) return err; @@ -694,8 +700,7 @@ static SHARED_DATA struct comp_driver_info comp_chain_dma_info = { UT_STATIC void sys_comp_chain_dma_init(void) { - comp_register(platform_shared_get(&comp_chain_dma_info, - sizeof(comp_chain_dma_info))); + comp_register(&comp_chain_dma_info); } DECLARE_MODULE(sys_comp_chain_dma_init); diff --git a/src/audio/codec/dts/dts.c b/src/audio/codec/dts/dts.c index cd1da3363517..bc4795055576 100644 --- a/src/audio/codec/dts/dts.c +++ b/src/audio/codec/dts/dts.c @@ -328,9 +328,15 @@ static int dts_codec_apply_config(struct processing_module *mod) /* Allow for multiple module_params to be packed into the data pointed to by config */ + param_header_size = sizeof(param->id) + sizeof(param->size); for (i = 0; i < config_data_size; param_number++) { + /* Need at least a param header in the remaining bytes to read id/size */ + if (config_data_size - i < param_header_size) { + comp_err(dev, "param header truncated"); + return -EINVAL; + } + param = (struct module_param *)((char *)config->data + i); - param_header_size = sizeof(param->id) + sizeof(param->size); /* If param->size is less than param_header_size, then this param is not valid */ if (param->size < param_header_size) { @@ -338,6 +344,13 @@ static int dts_codec_apply_config(struct processing_module *mod) return -EINVAL; } + /* The whole param (header + data) must fit in the remaining config data */ + if (param->size > config_data_size - i) { + comp_err(dev, "param size %u exceeds remaining %u", + param->size, config_data_size - i); + return -EINVAL; + } + /* Only process param->data if it has size greater than 0 */ if (param->size > param_header_size) { /* Calculate size of param->data */ diff --git a/src/audio/component.c b/src/audio/component.c index da597023cf73..66fbed1535b3 100644 --- a/src/audio/component.c +++ b/src/audio/component.c @@ -38,6 +38,13 @@ LOG_MODULE_REGISTER(component, CONFIG_SOF_LOG_LEVEL); static APP_SYSUSER_BSS SHARED_DATA struct comp_driver_list cd; +#ifdef CONFIG_SOF_USERSPACE_LL +struct comp_driver_list *comp_drivers_get(void) +{ + return &cd; +} +#endif + SOF_DEFINE_REG_UUID(component); DECLARE_TR_CTX(comp_tr, SOF_UUID(component_uuid), LOG_LEVEL_INFO); @@ -45,23 +52,22 @@ DECLARE_TR_CTX(comp_tr, SOF_UUID(component_uuid), LOG_LEVEL_INFO); int comp_register(struct comp_driver_info *drv) { struct comp_driver_list *drivers = comp_drivers_get(); - k_spinlock_key_t key; - key = k_spin_lock(&drivers->lock); + /* + * No locking needed: the driver list is only modified at FW boot, + * where module init runs serially on the primary core, and at + * runtime from the serialized IPC thread (library load). These + * never overlap, so concurrent modification is not possible. + */ list_item_prepend(&drv->list, &drivers->list); - k_spin_unlock(&drivers->lock, key); return 0; } void comp_unregister(struct comp_driver_info *drv) { - struct comp_driver_list *drivers = comp_drivers_get(); - k_spinlock_key_t key; - - key = k_spin_lock(&drivers->lock); + /* see comp_register() on why no locking is needed */ list_item_del(&drv->list); - k_spin_unlock(&drivers->lock, key); } int comp_set_adapter_ops(const struct comp_driver *drv, const struct module_interface *ops) @@ -187,10 +193,9 @@ EXPORT_SYMBOL(comp_set_state); void sys_comp_init(struct sof *sof) { - sof->comp_drivers = platform_shared_get(&cd, sizeof(cd)); + sof->comp_drivers = &cd; list_init(&sof->comp_drivers->list); - k_spinlock_init(&sof->comp_drivers->lock); } void comp_get_copy_limits(struct comp_buffer *source, diff --git a/src/audio/copier/copier.c b/src/audio/copier/copier.c index 93e018408bf3..33c404a52404 100644 --- a/src/audio/copier/copier.c +++ b/src/audio/copier/copier.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -142,11 +143,38 @@ __cold static int copier_init(struct processing_module *mod) assert_can_be_cold(); if (copier->gtw_cfg.config_length > 1) { + /* + * config_length is host-controlled and expressed in dwords. A + * huge value overflows the 32-bit size arithmetic below: the + * "<< 2" wraps around, which shrinks both cfg_total_size and the + * mod_zalloc() size to a small value and defeats the payload + * check further down. The gateway blob must fit within the + * received init payload, so reject anything larger than that + * before doing any size arithmetic on it. + */ + if (copier->gtw_cfg.config_length > md->cfg.size / sizeof(uint32_t)) { + comp_err(dev, "copier_init(): invalid gtw_cfg.config_length %u", + copier->gtw_cfg.config_length); + return -EINVAL; + } + /* one word already included in gateway_cfg struct hence subtraction */ gtw_cfg_var_size += (copier->gtw_cfg.config_length - 1) << 2; cfg_total_size += gtw_cfg_var_size; } + /* + * gtw_cfg.config_length is host-controlled; make sure the resulting + * copy length does not read past the init payload in the mailbox. + * cfg_total_size is at least sizeof(*copier), so this also rejects an + * empty (md->cfg.size == 0) or otherwise too-small init payload. + */ + if (cfg_total_size > md->cfg.size) { + comp_err(dev, "copier_init(): cfg size %zu exceeds init payload %zu", + cfg_total_size, md->cfg.size); + return -EINVAL; + } + cd = mod_zalloc(mod, sizeof(*cd) + gtw_cfg_var_size); if (!cd) return -ENOMEM; @@ -164,6 +192,16 @@ __cold static int copier_init(struct processing_module *mod) node_id = copier->gtw_cfg.node_id; /* copier is linked to gateway */ if (node_id.dw != IPC4_INVALID_NODE_ID) { + /* A gateway copier wires itself into the pipeline graph, so it needs a + * valid parent pipeline (base FW modules using IPC4_INVALID_PIPELINE_ID + * are exempt in comp_new_ipc4()). Reject here to avoid a NULL deref. + */ + if (!dev->pipeline) { + comp_err(dev, "gateway copier requires a valid parent pipeline"); + ret = -EINVAL; + goto error; + } + cd->direction = get_gateway_direction(node_id.f.dma_type); switch (node_id.f.dma_type) { @@ -360,7 +398,7 @@ static int copier_comp_trigger(struct comp_dev *dev, int cmd) { struct processing_module *mod = comp_mod(dev); struct copier_data *cd = module_get_private_data(mod); - struct sof_ipc_stream_posn posn; + struct sof_ipc_stream_posn posn = { 0 }; struct comp_dev *dai_copier; struct comp_buffer *buffer; uint32_t latency; @@ -785,9 +823,11 @@ __cold static int set_attenuation(struct comp_dev *dev, uint32_t data_offset, co assert_can_be_cold(); - /* only support attenuation in format of 32bit */ - if (data_offset > sizeof(uint32_t)) { - comp_err(dev, "attenuation data size %d is incorrect", data_offset); + /* only support attenuation in format of 32bit; the payload is + * dereferenced as a uint32_t below so it must be exactly that size + */ + if (data_offset != sizeof(uint32_t)) { + comp_err(dev, "attenuation data size %u is incorrect", data_offset); return -EINVAL; } @@ -824,7 +864,9 @@ __cold static int set_chmap(struct comp_dev *dev, const void *data, size_t data_ pcm_converter_func process; pcm_converter_func converters[IPC4_COPIER_MODULE_OUTPUT_PINS_COUNT]; int i; +#ifndef CONFIG_SOF_USERSPACE_LL uint32_t irq_flags; +#endif assert_can_be_cold(); @@ -878,15 +920,26 @@ __cold static int set_chmap(struct comp_dev *dev, const void *data, size_t data_ } } - /* Atomically update chmap, process and converters */ + /* Atomically update chmap, process and converters. + * In user-space builds irq_local_disable() is privileged, + * use the LL scheduler lock instead. + */ +#ifdef CONFIG_SOF_USERSPACE_LL + user_ll_lock_sched(dev->pipeline->core); +#else irq_local_disable(irq_flags); +#endif cd->dd[0]->chmap = chmap_cfg->channel_map; cd->dd[0]->process = process; for (i = 0; i < IPC4_COPIER_MODULE_OUTPUT_PINS_COUNT; i++) cd->converter[i] = converters[i]; +#ifdef CONFIG_SOF_USERSPACE_LL + user_ll_unlock_sched(dev->pipeline->core); +#else irq_local_enable(irq_flags); +#endif return 0; } @@ -930,8 +983,9 @@ __cold static int copier_get_configuration(struct processing_module *mod, struct copier_data *cd = module_get_private_data(mod); struct ipc4_llp_reading_extended llp_ext; struct comp_dev *dev = mod->dev; - struct sof_ipc_stream_posn posn; + struct sof_ipc_stream_posn posn = { 0 }; struct ipc4_llp_reading llp; + int ret; assert_can_be_cold(); @@ -961,7 +1015,9 @@ __cold static int copier_get_configuration(struct processing_module *mod, } /* get llp from dai */ - comp_position(dev, &posn); + ret = comp_position(dev, &posn); + if (ret < 0) + return ret; convert_u64_to_u32s(posn.comp_posn, &llp.llp_l, &llp.llp_u); convert_u64_to_u32s(posn.wallclock, &llp.wclk_l, &llp.wclk_u); @@ -991,7 +1047,9 @@ __cold static int copier_get_configuration(struct processing_module *mod, } /* get llp from dai */ - comp_position(dev, &posn); + ret = comp_position(dev, &posn); + if (ret < 0) + return ret; convert_u64_to_u32s(posn.comp_posn, &llp_ext.llp_reading.llp_l, &llp_ext.llp_reading.llp_u); diff --git a/src/audio/copier/copier_dai.c b/src/audio/copier/copier_dai.c index dfd2590c7108..109bc2a4a6b2 100644 --- a/src/audio/copier/copier_dai.c +++ b/src/audio/copier/copier_dai.c @@ -88,6 +88,16 @@ static int copier_alh_assign_dai_index(struct comp_dev *dev, switch (dai->type) { case SOF_DAI_INTEL_HDA: /* We use DAI_INTEL_HDA for ACE 2.0 platforms */ + /* + * alh_cfg.count is host-controlled and scales the config size + * and mapping[] walk below; bound it before any arithmetic so a + * crafted blob cannot read past the gateway config. + */ + if (alh_blob->alh_cfg.count > IPC4_ALH_MAX_NUMBER_OF_GTW) { + comp_err(mod->dev, "Invalid ALH count: %u", + alh_blob->alh_cfg.count); + return -EINVAL; + } alh_cfg_size = get_alh_config_size(alh_blob); dma_config = (uint8_t *)gtw_cfg_data + alh_cfg_size; dma_config_length = (cd->config.gtw_cfg.config_length << 2) - alh_cfg_size; @@ -108,7 +118,7 @@ static int copier_alh_assign_dai_index(struct comp_dev *dev, } dai_num = alh_blob->alh_cfg.count; - if (dai_num > IPC4_ALH_MAX_NUMBER_OF_GTW || dai_num < 0) { + if (dai_num > IPC4_ALH_MAX_NUMBER_OF_GTW || dai_num <= 0) { comp_err(mod->dev, "Invalid dai_count: %d", dai_num); return -EINVAL; } @@ -138,7 +148,7 @@ static int copier_alh_assign_dai_index(struct comp_dev *dev, } dai_num = alh_blob->alh_cfg.count; - if (dai_num > IPC4_ALH_MAX_NUMBER_OF_GTW || dai_num < 0) { + if (dai_num > IPC4_ALH_MAX_NUMBER_OF_GTW || dai_num <= 0) { comp_err(mod->dev, "Invalid dai_count: %d", dai_num); return -EINVAL; } @@ -327,7 +337,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_all_dma_configs_tlvs_only(&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/audio/copier/copier_gain.c b/src/audio/copier/copier_gain.c index fc9f6664add8..842f92997108 100644 --- a/src/audio/copier/copier_gain.c +++ b/src/audio/copier/copier_gain.c @@ -150,7 +150,7 @@ int copier_gain_dma_control(union ipc4_connector_node_id node, const char *confi } struct ipc4_copier_module_cfg *copier_cfg = cd->dd[0]->dai_spec_config; - const int channels = copier_cfg->base.audio_fmt.channels_count; + const uint32_t channels = copier_cfg->base.audio_fmt.channels_count; ret = copier_set_gain(dev, cd->dd[0]->gain_data, gain_data, channels); if (ret) @@ -162,7 +162,7 @@ int copier_gain_dma_control(union ipc4_connector_node_id node, const char *confi } int copier_set_gain(struct comp_dev *dev, struct copier_gain_params *gain_params, - struct gain_dma_control_data *gain_data, int channels) + struct gain_dma_control_data *gain_data, uint32_t channels) { uint16_t static_gain[MAX_GAIN_COEFFS_CNT]; int ret; @@ -172,12 +172,17 @@ int copier_set_gain(struct comp_dev *dev, struct copier_gain_params *gain_params return -EINVAL; } + if (channels == 0 || channels > MAX_GAIN_COEFFS_CNT) { + comp_err(dev, "invalid channels count %u", channels); + return -EINVAL; + } + /* Set gain coefficients */ comp_info(dev, "Update gain coefficients from DMA_CONTROL ipc"); size_t gain_coef_size = channels * sizeof(uint16_t); - ret = memcpy_s(static_gain, gain_coef_size, gain_data->gain_coeffs, + ret = memcpy_s(static_gain, sizeof(static_gain), gain_data->gain_coeffs, gain_coef_size); if (ret) { comp_err(dev, "memcpy_s failed with error %d", ret); diff --git a/src/audio/copier/copier_gain.h b/src/audio/copier/copier_gain.h index ec55ff999206..bd7cc5719f5b 100644 --- a/src/audio/copier/copier_gain.h +++ b/src/audio/copier/copier_gain.h @@ -8,6 +8,7 @@ #ifndef __SOF_COPIER_GAIN_H__ #define __SOF_COPIER_GAIN_H__ +#include #include #include #include @@ -219,7 +220,7 @@ enum copier_gain_state copier_gain_eval_state(struct copier_gain_params *gain_pa * @return 0 on success, otherwise a negative error code. */ int copier_set_gain(struct comp_dev *dev, struct copier_gain_params *gain_params, - struct gain_dma_control_data *gain_data, int channels); + struct gain_dma_control_data *gain_data, uint32_t channels); /** * Checks for unity gain mode. diff --git a/src/audio/copier/copier_generic.c b/src/audio/copier/copier_generic.c index 89805f25af0c..bb5918819e5b 100644 --- a/src/audio/copier/copier_generic.c +++ b/src/audio/copier/copier_generic.c @@ -330,6 +330,12 @@ void copier_update_params(struct copier_data *cd, struct comp_dev *dev, int j; j = IPC4_SRC_QUEUE_ID(buf_get_id(sink)); + /* src_queue id is host-controlled; bound it before indexing out_fmt[] */ + if (j >= IPC4_COPIER_MODULE_OUTPUT_PINS_COUNT) { + comp_err(dev, "invalid src_queue id %d", j); + continue; + } + ipc4_update_buffer_format(sink, &cd->out_fmt[j]); } diff --git a/src/audio/copier/copier_ipcgtw.c b/src/audio/copier/copier_ipcgtw.c index ed365c1e50d8..115c9a4a7e2d 100644 --- a/src/audio/copier/copier_ipcgtw.c +++ b/src/audio/copier/copier_ipcgtw.c @@ -223,6 +223,20 @@ __cold int copier_ipcgtw_create(struct processing_module *mod, return -EINVAL; } + /* config_length is in dwords; require enough dwords to cover the + * gateway config header and the blob read below. Compare dword counts + * (rather than scaling config_length by 4) so a large host-supplied + * value cannot overflow the multiplication on 32-bit size_t. + */ + if (gtw_cfg->config_length < + SOF_DIV_ROUND_UP(sizeof(struct ipc4_gateway_config_data) + + sizeof(struct ipc4_ipc_gateway_config_blob), + sizeof(uint32_t))) { + comp_err(dev, "ipc4_gateway_config_data too small: %u", + gtw_cfg->config_length); + return -EINVAL; + } + cd->ipc_gtw = true; /* The IPC gateway is treated as a host gateway */ diff --git a/src/audio/copier/host_copier.h b/src/audio/copier/host_copier.h index 71ce89cf315b..d00907959e8f 100644 --- a/src/audio/copier/host_copier.h +++ b/src/audio/copier/host_copier.h @@ -70,7 +70,6 @@ struct host_data { /* host position reporting related */ uint32_t host_size; /**< Host buffer size (in bytes) */ - uint32_t report_pos; /**< Position in current report period */ uint32_t local_pos; /**< Local position in host buffer */ uint32_t host_period_bytes; uint16_t stream_tag; @@ -106,7 +105,10 @@ struct host_data { /* stream info */ struct sof_ipc_stream_posn posn; /* TODO: update this */ +#if CONFIG_HOST_DMA_IPC_POSITION_UPDATES + uint32_t report_pos; /**< Position in current report period */ struct ipc_msg *msg; /**< host notification */ +#endif #if CONFIG_XRUN_NOTIFICATIONS_ENABLE bool xrun_notification_sent; #endif diff --git a/src/audio/crossover/crossover.c b/src/audio/crossover/crossover.c index 5c1dca7cc9aa..cbc45a04ec50 100644 --- a/src/audio/crossover/crossover.c +++ b/src/audio/crossover/crossover.c @@ -350,16 +350,40 @@ static int crossover_free(struct processing_module *mod) * \brief Verifies that the config is formatted correctly. * * The function can only be called after the buffers have been initialized. + * + * \param[in] mod Processing module owning the configuration. The blob + * under validation is read from the module's private data + * (\c cd->config); callers must store it there beforehand. + * \param[in] new_size Size in bytes reported by the framework for the blob; + * must match the size field embedded in the blob header. + * \return 0 on success, negative errno on invalid configuration. */ -static int crossover_validate_config(struct processing_module *mod, - struct sof_crossover_config *config) +static int crossover_validate_config(struct processing_module *mod, size_t new_size) { + struct comp_data *cd = module_get_private_data(mod); + struct sof_crossover_config *config = cd->config; struct comp_dev *dev = mod->dev; - uint32_t size = config->size; + size_t required_size; int32_t num_assigned_sinks; + int32_t num_lr4s; + uint32_t size; + + if (!config) { + comp_err(dev, "NULL config blob"); + return -EINVAL; + } - if (size > SOF_CROSSOVER_MAX_SIZE || !size) { - comp_err(dev, "size %d is invalid", size); + /* Reject truncated blobs before touching config->size: the framework- + * reported new_size must cover at least the fixed header. + */ + if (new_size < sizeof(*config)) { + comp_err(dev, "size %u is smaller than header", (uint32_t)new_size); + return -EINVAL; + } + + size = config->size; + if (size > SOF_CROSSOVER_MAX_SIZE || size != new_size) { + comp_err(dev, "size %u is invalid", size); return -EINVAL; } @@ -370,6 +394,17 @@ static int crossover_validate_config(struct processing_module *mod, return -EINVAL; } + /* Each channel reads 2 * num_lr4s biquads from config->coef[]; the + * runtime uses 1 LR4 pair for 2-way and 3 LR4 pairs otherwise. + * See tune/sof_crossover_generate_config.m script. + */ + num_lr4s = (config->num_sinks == CROSSOVER_2WAY_NUM_SINKS) ? 1 : 3; + required_size = sizeof(*config) + (size_t)num_lr4s * 2 * sizeof(struct sof_eq_iir_biquad); + if (size < required_size) { + comp_err(dev, "size %u too small for num_sinks %u", size, config->num_sinks); + return -EINVAL; + } + /* Align the crossover's sinks, to their respective configuration in * the config. */ @@ -448,6 +483,9 @@ static int crossover_process_audio_stream(struct processing_module *mod, uint32_t frames = input_buffers[0].size; uint32_t frame_bytes = audio_stream_frame_bytes(input_buffers[0].data); uint32_t processed_bytes; + struct sof_crossover_config *prev_config; + uint32_t prev_num_sinks; + size_t cfg_size; int ret; int i; @@ -455,7 +493,26 @@ static int crossover_process_audio_stream(struct processing_module *mod, /* Check for changed configuration */ if (comp_is_new_data_blob_available(cd->model_handler)) { - cd->config = comp_get_data_blob(cd->model_handler, NULL, NULL); + prev_config = cd->config; + prev_num_sinks = prev_config ? prev_config->num_sinks : 0; + + cd->config = comp_get_data_blob(cd->model_handler, &cfg_size, NULL); + ret = crossover_validate_config(mod, cfg_size); + if (ret < 0) + return ret; + + /* num_sinks must not change at runtime: cd->crossover_split was + * selected for the previous count, and a smaller value would + * cause the split function to write past the sink array. + * Reject the blob; the previous config and split stay in use. + */ + if (prev_num_sinks && cd->config->num_sinks != prev_num_sinks) { + comp_err(dev, "runtime num_sinks change %u -> %u not supported", + prev_num_sinks, cd->config->num_sinks); + cd->config = prev_config; + return -EINVAL; + } + ret = crossover_setup(mod, audio_stream_get_channels(source)); if (ret < 0) { comp_err(dev, "failed Crossover setup"); @@ -513,6 +570,7 @@ static int crossover_prepare(struct processing_module *mod, struct comp_buffer *source, *sink; size_t data_size; int channels; + int ret; comp_info(dev, "entry"); @@ -545,17 +603,16 @@ static int crossover_prepare(struct processing_module *mod, cd->config = comp_get_data_blob(cd->model_handler, &data_size, NULL); - /* Initialize Crossover */ - if (cd->config && - (!data_size || crossover_validate_config(mod, cd->config) < 0)) { - /* If the configuration is invalid fail the prepare */ - comp_err(dev, "invalid binary config format"); - return -EINVAL; - } - + /* A NULL cd->config is a supported operating mode: the component runs + * in passthrough (see the else-branch below), so only validate when a + * blob is actually present. + */ if (cd->config) { - int ret = crossover_setup(mod, channels); + ret = crossover_validate_config(mod, data_size); + if (ret < 0) + return ret; + ret = crossover_setup(mod, channels); if (ret < 0) { comp_err(dev, "setup failed"); return ret; diff --git a/src/audio/dai-legacy.c b/src/audio/dai-legacy.c index 655fa94afb86..8a9496c49121 100644 --- a/src/audio/dai-legacy.c +++ b/src/audio/dai-legacy.c @@ -467,7 +467,7 @@ int dai_common_params(struct dai_data *dd, struct comp_dev *dev, uint32_t period_bytes; uint32_t buffer_size; uint32_t addr_align; - uint32_t align; + uint32_t align = 0; int err; comp_dbg(dev, "dai_params()"); @@ -1134,8 +1134,7 @@ static SHARED_DATA struct comp_driver_info comp_dai_info = { UT_STATIC void sys_comp_dai_init(void) { - comp_register(platform_shared_get(&comp_dai_info, - sizeof(comp_dai_info))); + comp_register(&comp_dai_info); } DECLARE_MODULE(sys_comp_dai_init); diff --git a/src/audio/dai-zephyr.c b/src/audio/dai-zephyr.c index e49d3e0f0122..3b9a73510658 100644 --- a/src/audio/dai-zephyr.c +++ b/src/audio/dai-zephyr.c @@ -196,6 +196,15 @@ __cold int dai_set_config(struct dai *dai, struct ipc_config_dai *common_config, cfg.type = DAI_AMD_SDW; cfg_params = &sof_cfg->acpsdw; break; + case SOF_DAI_AMD_HS: + case SOF_DAI_AMD_HS_VIRTUAL: + case SOF_DAI_AMD_SP: + case SOF_DAI_AMD_SP_VIRTUAL: + case SOF_DAI_AMD_BT: + case SOF_DAI_AMD_TDM: + cfg.type = DAI_AMD_TDM; + cfg_params = &sof_cfg->acptdm; + break; case SOF_DAI_INTEL_UAOL: cfg.type = DAI_INTEL_UAOL; cfg.channels = common_config->gtw_fmt->channels_count; @@ -226,58 +235,90 @@ __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) +{ + const struct dai_properties *props_p; + int ret; + + ret = dai_get_properties_copy(dai->dev, direction, stream_id, props); + + /* + * User LL builds require DAI drivers to implement the copy method. + * Allow fallback to dai_get_properties() in kernel LL builds. + */ + if (ret == -ENOSYS && !IS_ENABLED(CONFIG_SOF_USERSPACE_LL)) { + k_spinlock_key_t key = k_spin_lock(&dai->lock); + + /* legacy support for drivers that do not implement copy */ + props_p = dai_get_properties(dai->dev, direction, stream_id); + if (props_p) { + *props = *props_p; + ret = 0; + } else { + ret = -ENOENT; + } + + k_spin_unlock(&dai->lock, key); + } + + return ret; +} + /* called from ipc/ipc3/dai.c */ int dai_get_handshake(struct dai *dai, int direction, int stream_id) { - k_spinlock_key_t key = k_spin_lock(&dai->lock); - const struct dai_properties *props = dai_get_properties(dai->dev, direction, - stream_id); - int hs_id = props->dma_hs_id; + struct dai_properties props; + int ret; - k_spin_unlock(&dai->lock, key); + ret = dai_get_properties_safe(dai, direction, stream_id, &props); + if (ret < 0) + return ret; - return hs_id; + return props.dma_hs_id; } /* called from ipc/ipc3/dai.c and ipc/ipc4/dai.c */ int dai_get_fifo_depth(struct dai *dai, int direction) { - const struct dai_properties *props; - k_spinlock_key_t key; - int fifo_depth; + struct dai_properties props; + int ret; if (!dai) return 0; - key = k_spin_lock(&dai->lock); - props = dai_get_properties(dai->dev, direction, 0); - fifo_depth = props->fifo_depth; - k_spin_unlock(&dai->lock, key); + ret = dai_get_properties_safe(dai, direction, 0, &props); - return fifo_depth; + if (ret < 0) + return 0; + + return props.fifo_depth; } int dai_get_stream_id(struct dai *dai, int direction) { - k_spinlock_key_t key = k_spin_lock(&dai->lock); - const struct dai_properties *props = dai_get_properties(dai->dev, direction, 0); - int stream_id = props->stream_id; + struct dai_properties props; + int ret; - k_spin_unlock(&dai->lock, key); + ret = dai_get_properties_safe(dai, direction, 0, &props); - return stream_id; + if (ret < 0) + return ret; + + return props.stream_id; } static int dai_get_fifo(struct dai *dai, int direction, int stream_id) { - k_spinlock_key_t key = k_spin_lock(&dai->lock); - const struct dai_properties *props = dai_get_properties(dai->dev, direction, - stream_id); - int fifo_address = props->fifo_address; + struct dai_properties props; + int ret; - k_spin_unlock(&dai->lock, key); + ret = dai_get_properties_safe(dai, direction, stream_id, &props); + + if (ret < 0) + return ret; - return fifo_address; + return props.fifo_address; } /* this is called by DMA driver every time descriptor has completed */ @@ -977,7 +1018,7 @@ static int dai_set_dma_buffer(struct dai_data *dd, struct comp_dev *dev, uint32_t buffer_size; uint32_t buffer_size_preferred; uint32_t addr_align; - uint32_t align; + uint32_t align = 0; int err; comp_dbg(dev, "entry"); @@ -1973,17 +2014,17 @@ static int dai_ts_stop_op(struct comp_dev *dev) uint32_t dai_get_init_delay_ms(struct dai *dai) { - const struct dai_properties *props; - k_spinlock_key_t key; - uint32_t init_delay; + struct dai_properties props; + uint32_t init_delay = 0; + int ret; if (!dai) return 0; - key = k_spin_lock(&dai->lock); - props = dai_get_properties(dai->dev, 0, 0); - init_delay = props->reg_init_delay; - k_spin_unlock(&dai->lock, key); + ret = dai_get_properties_safe(dai, 0, 0, &props); + + if (!ret) + init_delay = props.reg_init_delay; return init_delay; } @@ -2057,7 +2098,7 @@ static SHARED_DATA struct comp_driver_info comp_dai_info = { UT_STATIC void sys_comp_dai_init(void) { - comp_register(platform_shared_get(&comp_dai_info, sizeof(comp_dai_info))); + comp_register(&comp_dai_info); } DECLARE_MODULE(sys_comp_dai_init); diff --git a/src/audio/data_blob.c b/src/audio/data_blob.c index 399244106f95..94ea19e829a7 100644 --- a/src/audio/data_blob.c +++ b/src/audio/data_blob.c @@ -330,6 +330,14 @@ int ipc4_comp_data_blob_set(struct comp_data_blob_handler *blob_handler, "data_offset = %d", data_offset); + /* Reject a new first_block mid-transfer: reusing stale data_new while + * overwriting new_data_size defeats the memcpy_s bound (heap overflow). + */ + if (blob_handler->data_new && first_block) { + comp_err(blob_handler->dev, "busy with previous request"); + return -EBUSY; + } + /* in case when the current package is the first, we should allocate * memory for whole model data */ @@ -365,8 +373,16 @@ int ipc4_comp_data_blob_set(struct comp_data_blob_handler *blob_handler, valid_data_size = last_block ? data_offset : MAILBOX_DSPBOX_SIZE; ret = memcpy_s((char *)blob_handler->data_new, - valid_data_size, data, valid_data_size); - assert(!ret); + blob_handler->new_data_size, data, valid_data_size); + if (ret) { + comp_err(blob_handler->dev, "failed to copy fragment"); + blob_handler->free(blob_handler->data_new); + blob_handler->data_new = NULL; + blob_handler->new_data_size = 0; + blob_handler->data_pos = 0; + blob_handler->data_ready = false; + return ret; + } blob_handler->data_pos += valid_data_size; } else { @@ -391,8 +407,17 @@ int ipc4_comp_data_blob_set(struct comp_data_blob_handler *blob_handler, valid_data_size = blob_handler->new_data_size - data_offset; ret = memcpy_s((char *)blob_handler->data_new + data_offset, - valid_data_size, data, valid_data_size); - assert(!ret); + blob_handler->new_data_size - data_offset, + data, valid_data_size); + if (ret) { + comp_err(blob_handler->dev, "failed to copy fragment"); + blob_handler->free(blob_handler->data_new); + blob_handler->data_new = NULL; + blob_handler->new_data_size = 0; + blob_handler->data_pos = 0; + blob_handler->data_ready = false; + return ret; + } blob_handler->data_pos += valid_data_size; } @@ -603,6 +628,18 @@ int comp_data_blob_get_cmd(struct comp_data_blob_handler *blob_handler, return -EINVAL; } + /* Bound data_pos against data_size: host-controlled num_elems + * advances it per fragment and could leak adjacent heap. + */ + if (blob_handler->data_pos >= blob_handler->data_size || + cdata->num_elems > blob_handler->data_size - blob_handler->data_pos) { + comp_err(blob_handler->dev, + "out of bounds read: pos %u elems %u size %u", + blob_handler->data_pos, cdata->num_elems, + blob_handler->data_size); + return -EINVAL; + } + /* copy required size of data */ ret = memcpy_s(cdata->data->data, size, (char *)blob_handler->data + blob_handler->data_pos, @@ -624,12 +661,13 @@ EXPORT_SYMBOL(comp_data_blob_get_cmd); static void *default_alloc(size_t size) { - return rballoc(SOF_MEM_FLAG_USER, size); + return sof_heap_alloc(sof_sys_user_heap_get(), + SOF_MEM_FLAG_USER | SOF_MEM_FLAG_LARGE_BUFFER, size, 0); } static void default_free(void *buf) { - rfree(buf); + sof_heap_free(sof_sys_user_heap_get(), buf); } struct comp_data_blob_handler * @@ -641,10 +679,11 @@ comp_data_blob_handler_new_ext(struct comp_dev *dev, bool single_blob, comp_dbg(dev, "entry"); - handler = rzalloc(SOF_MEM_FLAG_USER, - sizeof(struct comp_data_blob_handler)); + handler = sof_heap_alloc(sof_sys_user_heap_get(), SOF_MEM_FLAG_USER, + sizeof(struct comp_data_blob_handler), 0); if (handler) { + memset(handler, 0, sizeof(*handler)); handler->dev = dev; handler->single_blob = single_blob; handler->alloc = alloc ? alloc : default_alloc; @@ -662,6 +701,6 @@ void comp_data_blob_handler_free(struct comp_data_blob_handler *blob_handler) comp_free_data_blob(blob_handler); - rfree(blob_handler); + sof_heap_free(sof_sys_user_heap_get(), blob_handler); } EXPORT_SYMBOL(comp_data_blob_handler_free); diff --git a/src/audio/dcblock/dcblock.c b/src/audio/dcblock/dcblock.c index 5f0935a2bc29..15e6139b7a89 100644 --- a/src/audio/dcblock/dcblock.c +++ b/src/audio/dcblock/dcblock.c @@ -205,7 +205,11 @@ static int dcblock_prepare(struct processing_module *mod, cd->source_format, cd->sink_format); cd->config = comp_get_data_blob(cd->model_handler, &data_size, NULL); - if (cd->config && data_size > 0) + /* dcblock_copy_coefficients() copies sizeof(R_coeffs) from the blob, so + * require the blob to actually hold that many bytes; fall back to + * passthrough otherwise instead of over-reading the blob + */ + if (cd->config && data_size >= sizeof(cd->R_coeffs)) dcblock_copy_coefficients(mod); else dcblock_set_passthrough(mod); diff --git a/src/audio/drc/drc.c b/src/audio/drc/drc.c index 007336fb2115..a727b68d55fd 100644 --- a/src/audio/drc/drc.c +++ b/src/audio/drc/drc.c @@ -139,6 +139,20 @@ static int drc_setup(struct processing_module *mod, uint16_t channels, uint32_t return drc_set_pre_delay_time(&cd->state, cd->config->params.pre_delay_time, rate); } +static int drc_validator(struct comp_dev *dev, void *new_data, uint32_t new_data_size) +{ + struct sof_drc_config *config = new_data; + + if (new_data_size != sizeof(struct sof_drc_config) || + new_data_size != config->size) { + comp_err(dev, "invalid configuration blob, size %u, expected %zu", + new_data_size, sizeof(struct sof_drc_config)); + return -EINVAL; + } + + return 0; +} + /* * End of DRC setup code. Next the standard component methods. */ @@ -168,6 +182,11 @@ __cold static int drc_init(struct processing_module *mod) goto cd_fail; } + /* Reject malformed blobs at IPC time so a bad run-time update cannot + * replace the working configuration. + */ + comp_data_blob_set_validator(cd->model_handler, drc_validator); + drc_reset_state(mod, &cd->state); /* Initialize DRC to enabled. If defined by topology, a control may set @@ -326,7 +345,6 @@ static int drc_prepare(struct processing_module *mod, struct drc_comp_data *cd = module_get_private_data(mod); struct comp_buffer *sourceb, *sinkb; struct comp_dev *dev = mod->dev; - size_t data_size; int channels; int rate; int ret; @@ -352,8 +370,9 @@ static int drc_prepare(struct processing_module *mod, /* Initialize DRC */ comp_info(dev, "source_format=%d", cd->source_format); - cd->config = comp_get_data_blob(cd->model_handler, &data_size, NULL); - if (cd->config && data_size > 0) { + cd->config = comp_get_data_blob(cd->model_handler, NULL, NULL); + + if (cd->config) { ret = drc_setup(mod, channels, rate); if (ret < 0) { comp_err(dev, "error: drc_setup failed."); diff --git a/src/audio/drc/drc_hifi4.c b/src/audio/drc/drc_hifi4.c index b2252058e885..a4c32c0deb15 100644 --- a/src/audio/drc/drc_hifi4.c +++ b/src/audio/drc/drc_hifi4.c @@ -116,7 +116,7 @@ void drc_update_detector_average(struct drc_state *state, int nch) { ae_f32 detector_average = state->detector_average; /* Q2.30 */ - ae_int32 abs_input_array[DRC_DIVISION_FRAMES]; /* Q1.31 */ + ae_int32 abs_input_array[DRC_DIVISION_FRAMES] __attribute__((aligned(8))); /* Q1.31 */ ae_int32 *abs_input_array_p; int div_start, i, ch; ae_int16 *sample16_p; /* for s16 format case */ diff --git a/src/audio/eq_fir/eq_fir.c b/src/audio/eq_fir/eq_fir.c index f08b25315616..6c4c3d630562 100644 --- a/src/audio/eq_fir/eq_fir.c +++ b/src/audio/eq_fir/eq_fir.c @@ -72,12 +72,13 @@ static void eq_fir_free_delaylines(struct processing_module *mod) } static int eq_fir_init_coef(struct comp_dev *dev, struct sof_eq_fir_config *config, - struct fir_state_32x16 *fir, int nch) + size_t config_size, struct fir_state_32x16 *fir, int nch) { struct sof_fir_coef_data *lookup[SOF_EQ_FIR_MAX_RESPONSES]; struct sof_fir_coef_data *eq; int16_t *assign_response; int16_t *coef_data; + size_t coef_words_max; size_t size_sum = 0; int resp = 0; int i; @@ -101,27 +102,70 @@ static int eq_fir_init_coef(struct comp_dev *dev, struct sof_eq_fir_config *conf config->number_of_responses, config->channels_in_config, nch); /* Sanity checks */ + if (config->size != config_size) { + comp_err(dev, "Incorrect configuration blob size"); + return -EINVAL; + } + if (nch > PLATFORM_MAX_CHANNELS || config->channels_in_config > PLATFORM_MAX_CHANNELS || !config->channels_in_config) { comp_err(dev, "invalid channels count"); return -EINVAL; } + /* channels_in_config indexes into a int16_t array. An odd count would + * leave the coefficient area at a 2-byte alignment, breaking the + * 4-byte aligned int32_t loads in the optimized FIR kernels. + */ + if (config->channels_in_config & 0x1) { + comp_err(dev, "channels_in_config %u must be even", + config->channels_in_config); + return -EINVAL; + } if (config->number_of_responses > SOF_EQ_FIR_MAX_RESPONSES) { comp_err(dev, "# of resp exceeds max"); return -EINVAL; } + /* Compute the size of the coefficient area in int16_t words from the + * blob's self-declared size. The blob layout is: + * sizeof(*config) header bytes + * channels_in_config int16_t assign_response[] + * coefficient data[] + */ + if (config->size < sizeof(*config) || + config->size - sizeof(*config) < + (size_t)config->channels_in_config * sizeof(int16_t)) { + comp_err(dev, "config size %u too small", config->size); + return -EINVAL; + } + coef_words_max = (config->size - sizeof(*config)) / sizeof(int16_t) - + config->channels_in_config; + /* Collect index of response start positions in all_coefficients[] */ j = 0; assign_response = ASSUME_ALIGNED(&config->data[0], 4); - coef_data = ASSUME_ALIGNED(&config->data[config->channels_in_config], - 4); + coef_data = ASSUME_ALIGNED(&config->data[config->channels_in_config], 4); for (i = 0; i < SOF_EQ_FIR_MAX_RESPONSES; i++) { if (i < config->number_of_responses) { + /* Header must fit before reading length */ + if (j + SOF_FIR_COEF_NHEADER > coef_words_max) { + comp_err(dev, "response %d header out of bounds", i); + return -EINVAL; + } eq = (struct sof_fir_coef_data *)&coef_data[j]; + /* Bound length so it is valid and the coefficient data + * stays within the blob. + */ + if (eq->length <= 0 || eq->length > SOF_FIR_MAX_LENGTH || + (eq->length & 0x3) || + j + SOF_FIR_COEF_NHEADER + eq->length > coef_words_max) { + comp_err(dev, "response %d length %d out of bounds", + i, eq->length); + return -EINVAL; + } lookup[i] = eq; - j += SOF_FIR_COEF_NHEADER + coef_data[j]; + j += SOF_FIR_COEF_NHEADER + eq->length; } else { lookup[i] = NULL; } @@ -209,7 +253,7 @@ static int eq_fir_setup(struct processing_module *mod, int nch) cd->nch = nch; /* Set coefficients for each channel EQ from coefficient blob */ - delay_size = eq_fir_init_coef(dev, cd->config, cd->fir, nch); + delay_size = eq_fir_init_coef(dev, cd->config, cd->config_size, cd->fir, nch); if (delay_size < 0) return delay_size; /* Contains error code */ @@ -234,9 +278,25 @@ static int eq_fir_setup(struct processing_module *mod, int nch) return 0; } +static int eq_fir_check_blob_size(struct comp_dev *dev, size_t size) +{ + if (size < sizeof(struct sof_eq_fir_config) || size > SOF_EQ_FIR_MAX_SIZE) { + comp_err(dev, "invalid configuration blob, size %zu", size); + return -EINVAL; + } + + return 0; +} + static int eq_fir_validator(struct comp_dev *dev, void *new_data, uint32_t new_data_size) { - return eq_fir_init_coef(dev, new_data, NULL, -1); + int ret; + + ret = eq_fir_check_blob_size(dev, new_data_size); + if (ret < 0) + return ret; + + return eq_fir_init_coef(dev, new_data, new_data_size, NULL, -1); } /* @@ -332,7 +392,9 @@ static int eq_fir_process(struct processing_module *mod, /* Check for changed configuration */ if (comp_is_new_data_blob_available(cd->model_handler)) { - cd->config = comp_get_data_blob(cd->model_handler, NULL, NULL); + cd->config = comp_get_data_blob(cd->model_handler, &cd->config_size, NULL); + if (!cd->config || eq_fir_check_blob_size(mod->dev, cd->config_size) < 0) + return -EINVAL; ret = eq_fir_setup(mod, audio_stream_get_channels(source)); if (ret < 0) { comp_err(mod->dev, "failed FIR setup"); @@ -384,7 +446,6 @@ static int eq_fir_prepare(struct processing_module *mod, int channels; enum sof_ipc_frame frame_fmt; int ret = 0; - size_t data_size; comp_dbg(dev, "entry"); @@ -407,8 +468,11 @@ static int eq_fir_prepare(struct processing_module *mod, frame_fmt = audio_stream_get_frm_fmt(&sourceb->stream); cd->eq_fir_func = eq_fir_passthrough; - cd->config = comp_get_data_blob(cd->model_handler, &data_size, NULL); - if (cd->config && data_size > 0) { + cd->config = comp_get_data_blob(cd->model_handler, &cd->config_size, NULL); + if (cd->config) { + if (eq_fir_check_blob_size(dev, cd->config_size) < 0) + return -EINVAL; + ret = eq_fir_setup(mod, channels); if (ret < 0) comp_err(dev, "eq_fir_setup failed."); diff --git a/src/audio/eq_fir/eq_fir.h b/src/audio/eq_fir/eq_fir.h index 921d39939d69..5af74dc5674a 100644 --- a/src/audio/eq_fir/eq_fir.h +++ b/src/audio/eq_fir/eq_fir.h @@ -36,6 +36,7 @@ struct comp_data { struct comp_data_blob_handler *model_handler; struct sof_eq_fir_config *config; int32_t *fir_delay; /**< pointer to allocated RAM */ + size_t config_size; /**< configuration size */ size_t fir_delay_size; /**< allocated size */ void (*eq_fir_func)(struct fir_state_32x16 fir[], struct input_stream_buffer *bsource, diff --git a/src/audio/eq_iir/eq_iir.c b/src/audio/eq_iir/eq_iir.c index 016c2c8caf82..a9a4bc4aa24a 100644 --- a/src/audio/eq_iir/eq_iir.c +++ b/src/audio/eq_iir/eq_iir.c @@ -63,6 +63,11 @@ static int eq_iir_init(struct processing_module *mod) return -ENOMEM; } + /* Reject malformed blobs at IPC time so a bad run-time update cannot + * replace the working configuration. + */ + comp_data_blob_set_validator(cd->model_handler, eq_iir_validate_config); + for (i = 0; i < PLATFORM_MAX_CHANNELS; i++) iir_reset_df1(&cd->iir[i]); @@ -117,9 +122,15 @@ static int eq_iir_process(struct processing_module *mod, uint32_t frame_count = input_buffers[0].size; int ret; - /* Check for changed configuration */ + /* Check for changed configuration. The IPC-time validator installed + * in eq_iir_init() has already structurally validated the blob, so + * only NULL needs to be guarded here. + */ if (comp_is_new_data_blob_available(cd->model_handler)) { - cd->config = comp_get_data_blob(cd->model_handler, NULL, NULL); + cd->config = comp_get_data_blob(cd->model_handler, &cd->config_size, NULL); + if (!cd->config) + return -EINVAL; + ret = eq_iir_new_blob(mod, audio_stream_get_frm_fmt(source), audio_stream_get_frm_fmt(sink), audio_stream_get_channels(source)); @@ -158,7 +169,6 @@ static int eq_iir_prepare(struct processing_module *mod, struct comp_dev *dev = mod->dev; enum sof_ipc_frame source_format; enum sof_ipc_frame sink_format; - size_t data_size; int channels; int ret = 0; @@ -183,7 +193,7 @@ static int eq_iir_prepare(struct processing_module *mod, source_format = audio_stream_get_frm_fmt(&sourceb->stream); sink_format = audio_stream_get_frm_fmt(&sinkb->stream); - cd->config = comp_get_data_blob(cd->model_handler, &data_size, NULL); + cd->config = comp_get_data_blob(cd->model_handler, &cd->config_size, NULL); /* Initialize EQ */ comp_info(dev, "source_format=%d, sink_format=%d", @@ -192,7 +202,7 @@ static int eq_iir_prepare(struct processing_module *mod, eq_iir_set_passthrough_func(cd, source_format, sink_format); /* Initialize EQ */ - if (cd->config && data_size > 0) { + if (cd->config) { ret = eq_iir_new_blob(mod, source_format, sink_format, channels); if (ret) return ret; diff --git a/src/audio/eq_iir/eq_iir.h b/src/audio/eq_iir/eq_iir.h index 12d888d66594..7b9bf77976f1 100644 --- a/src/audio/eq_iir/eq_iir.h +++ b/src/audio/eq_iir/eq_iir.h @@ -39,6 +39,7 @@ struct comp_data { struct comp_data_blob_handler *model_handler; struct sof_eq_iir_config *config; int32_t *iir_delay; /**< pointer to allocated RAM */ + size_t config_size; /**< configuration size */ size_t iir_delay_size; /**< allocated size */ eq_iir_func eq_iir_func; /**< processing function */ }; @@ -70,5 +71,7 @@ void eq_iir_pass(struct processing_module *mod, struct input_stream_buffer *bsou int eq_iir_setup(struct processing_module *mod, int nch); +int eq_iir_validate_config(struct comp_dev *dev, void *new_data, uint32_t new_data_size); + void eq_iir_free_delaylines(struct processing_module *mod); #endif /* __SOF_AUDIO_EQ_IIR_EQ_IIR_H__ */ diff --git a/src/audio/eq_iir/eq_iir_generic.c b/src/audio/eq_iir/eq_iir_generic.c index fd3485a28eca..b9f723b0c778 100644 --- a/src/audio/eq_iir/eq_iir_generic.c +++ b/src/audio/eq_iir/eq_iir_generic.c @@ -180,6 +180,171 @@ void eq_iir_s32_default(struct processing_module *mod, struct input_stream_buffe } #endif /* CONFIG_FORMAT_S32LE */ +static int eq_iir_blob_words_max(struct comp_dev *dev, + const struct sof_eq_iir_config *config, + size_t blob_size, + uint32_t *coef_words_max) +{ + size_t payload_bytes; + + /* Compute the size of the coefficient area in int32_t words from the + * framework-reported blob size. The blob layout is: + * sizeof(*config) header bytes + * channels_in_config int32_t assign_response[] + * coefficient data[] + * channels_in_config is bounded above, so the multiply fits in size_t. + * The blob's self-declared config->size is cross-checked against the + * authoritative blob_size so all later parsing stays within the buffer. + */ + if (blob_size < sizeof(*config) || config->size != blob_size) { + comp_err(dev, "blob size %zu / header size %u mismatch or too small", + blob_size, config->size); + return -EINVAL; + } + payload_bytes = blob_size - sizeof(*config); + if (payload_bytes % sizeof(int32_t) || + payload_bytes < (size_t)config->channels_in_config * sizeof(int32_t)) { + comp_err(dev, "blob size %zu misaligned or too small", blob_size); + return -EINVAL; + } + *coef_words_max = payload_bytes / sizeof(int32_t) - config->channels_in_config; + return 0; +} + +/** + * @brief Parse one response header from the coefficient blob. + * + * Reads the response at word offset *j in @p coef_data, checks that both + * the fixed header and the following biquad sections fit inside the + * @p coef_words_max blob budget, and advances *j past this response so + * the next call continues where this one left off. On success @p *eq_out + * is set to point at the in-place header; callers that only want to walk + * the blob for validation can discard that pointer. + * + * @param dev Component device, used for error logging. + * @param idx Response index, used in error messages. + * @param coef_data Base of the coefficient word array in the blob. + * @param coef_words_max Total words available in @p coef_data. + * @param j In/out cursor in words; advanced past this response. + * @param eq_out Out; pointer to the parsed response header. + * + * @return 0 on success, -EINVAL if the header or sections would run off + * the end of the blob or num_sections exceeds the platform limit. + */ +static int eq_iir_init_response(struct comp_dev *dev, int idx, + int32_t *coef_data, uint32_t coef_words_max, + uint32_t *j, struct sof_eq_iir_header **eq_out) +{ + struct sof_eq_iir_header *eq; + uint32_t header_end = *j + SOF_EQ_IIR_NHEADER; + uint32_t section_end; + + /* Header must fit before reading num_sections */ + if (header_end > coef_words_max) { + comp_err(dev, "response %d header out of bounds", idx); + return -EINVAL; + } + eq = (struct sof_eq_iir_header *)&coef_data[*j]; + /* Bound num_sections so the multiply cannot overflow and the section + * data stays within the blob. + */ + section_end = header_end + (uint32_t)SOF_EQ_IIR_NBIQUAD * eq->num_sections; + if (eq->num_sections > SOF_EQ_IIR_BIQUADS_MAX || section_end > coef_words_max) { + comp_err(dev, "response %d num_sections %u out of bounds", + idx, eq->num_sections); + return -EINVAL; + } + *eq_out = eq; + *j = section_end; + return 0; +} + +/* Validate the config blob layout and, if lookup is non-NULL, populate it + * with pointers to each response header. Pass lookup = NULL to validate only. + */ +static int eq_iir_walk_config(struct comp_dev *dev, + struct sof_eq_iir_config *config, + size_t config_size, + struct sof_eq_iir_header **lookup) +{ + struct sof_eq_iir_header *eq; + uint32_t coef_words_max; + int32_t *coef_data; + int ret; + int i; + uint32_t j; + + if (config->channels_in_config > PLATFORM_MAX_CHANNELS || + !config->channels_in_config) { + comp_err(dev, "invalid channels_in_config %u", config->channels_in_config); + return -EINVAL; + } + if (config->number_of_responses > SOF_EQ_IIR_MAX_RESPONSES) { + comp_err(dev, "# of resp %u exceeds max", config->number_of_responses); + return -EINVAL; + } + + ret = eq_iir_blob_words_max(dev, config, config_size, &coef_words_max); + if (ret < 0) + return ret; + + j = 0; + coef_data = ASSUME_ALIGNED(&config->data[config->channels_in_config], 4); + if (lookup) + memset(lookup, 0, SOF_EQ_IIR_MAX_RESPONSES * sizeof(*lookup)); + + for (i = 0; i < config->number_of_responses; i++) { + /* Bounds-check response i, advance the walk cursor j past it, + * and get a pointer to its in-place header. Stored in lookup[] + * for later use, or discarded when we are walking the blob + * only to validate it. + */ + ret = eq_iir_init_response(dev, i, coef_data, coef_words_max, &j, &eq); + if (ret < 0) + return ret; + if (lookup) + lookup[i] = eq; + } + + return 0; +} + +int eq_iir_validate_config(struct comp_dev *dev, void *new_data, uint32_t new_data_size) +{ + struct sof_eq_iir_config *config = new_data; + int32_t *assign_response; + int32_t resp; + int ret; + int i; + + if (new_data_size < sizeof(struct sof_eq_iir_config) || + new_data_size > SOF_EQ_IIR_MAX_SIZE) { + comp_err(dev, "invalid configuration blob, size %u", new_data_size); + return -EINVAL; + } + + ret = eq_iir_walk_config(dev, config, new_data_size, NULL); + if (ret < 0) + return ret; + + /* Validate every assign_response[] entry that the per-channel loop in + * eq_iir_init_coef() could pick up. Entries beyond channels_in_config + * reuse the last assigned value, so checking [0, channels_in_config) + * covers all reachable nch. + */ + assign_response = ASSUME_ALIGNED(&config->data[0], 4); + for (i = 0; i < config->channels_in_config; i++) { + resp = assign_response[i]; + if (resp >= 0 && resp >= config->number_of_responses) { + comp_err(dev, "assign_response[%d] = %d exceeds %u", + i, resp, config->number_of_responses); + return -EINVAL; + } + } + + return 0; +} + static int eq_iir_init_coef(struct processing_module *mod, int nch) { struct comp_data *cd = module_get_private_data(mod); @@ -188,45 +353,26 @@ static int eq_iir_init_coef(struct processing_module *mod, int nch) struct sof_eq_iir_header *lookup[SOF_EQ_IIR_MAX_RESPONSES]; struct sof_eq_iir_header *eq; int32_t *assign_response; - int32_t *coef_data; int size_sum = 0; int resp = 0; int i; - int j; int s; + int ret; comp_info(mod->dev, "%u responses, %u channels, stream %d channels", config->number_of_responses, config->channels_in_config, nch); - /* Sanity checks */ - if (nch > PLATFORM_MAX_CHANNELS || - config->channels_in_config > PLATFORM_MAX_CHANNELS || - !config->channels_in_config) { - comp_err(mod->dev, "invalid channels count"); - return -EINVAL; - } - if (config->number_of_responses > SOF_EQ_IIR_MAX_RESPONSES) { - comp_err(mod->dev, "# of resp exceeds max"); + if (nch > PLATFORM_MAX_CHANNELS) { + comp_err(mod->dev, "invalid stream channels %d", nch); return -EINVAL; } - /* Collect index of response start positions in all_coefficients[] */ - j = 0; - assign_response = ASSUME_ALIGNED(&config->data[0], 4); - coef_data = ASSUME_ALIGNED(&config->data[config->channels_in_config], - 4); - for (i = 0; i < SOF_EQ_IIR_MAX_RESPONSES; i++) { - if (i < config->number_of_responses) { - eq = (struct sof_eq_iir_header *)&coef_data[j]; - lookup[i] = eq; - j += SOF_EQ_IIR_NHEADER - + SOF_EQ_IIR_NBIQUAD * eq->num_sections; - } else { - lookup[i] = NULL; - } - } + ret = eq_iir_walk_config(mod->dev, config, cd->config_size, lookup); + if (ret < 0) + return ret; /* Initialize 1st phase */ + assign_response = ASSUME_ALIGNED(&config->data[0], 4); for (i = 0; i < nch; i++) { /* Check for not reading past blob response to channel assign * map. The previous channel response is assigned for any @@ -318,7 +464,10 @@ int eq_iir_setup(struct processing_module *mod, int nch) /* Free existing IIR channels data if it was allocated */ eq_iir_free_delaylines(mod); - /* Set coefficients for each channel EQ from coefficient blob */ + /* Set coefficients for each channel EQ from coefficient blob. + * eq_iir_init_coef() / eq_iir_blob_words_max() perform all blob size + * sanity checks, including config->size vs cd->config_size. + */ delay_size = eq_iir_init_coef(mod, nch); if (delay_size < 0) return delay_size; /* Contains error code */ diff --git a/src/audio/eq_iir/tune/sof_mls_freq_resp.m b/src/audio/eq_iir/tune/sof_mls_freq_resp.m index a30864d93a3b..ccb67c756102 100644 --- a/src/audio/eq_iir/tune/sof_mls_freq_resp.m +++ b/src/audio/eq_iir/tune/sof_mls_freq_resp.m @@ -1,13 +1,18 @@ -function [f, m_db] = sof_mls_freq_resp(id) +function [f, m_db] = sof_mls_freq_resp(id, play_config_fn, rec_config_fn, data_dir) %% Measure frequency response with MLS test signal % -% [f, m] = mls_freq_resp(id) +% [f, m] = sof_mls_freq_resp(id, play_config_fn, rec_config_fn, data_dir) % % Input parameters -% id - A string identifier for test case. An id 'selftest' is for special -% usage. It calculates response of filtered MLS signal and computes -% the measurement vs. known. Deviation is reported as error. It can -% be useful if the internal MLS measurement parameters are adjusted. +% id - A string identifier for test case. An id 'selftest' is for +% special usage. It calculates response of filtered MLS signal +% and computes the measurement vs. known. Deviation is +% reported as error. It can be useful if the internal MLS +% measurement parameters are adjusted. +% play_config_fn - Playback config file (default 'mls_play_config.txt'). +% rec_config_fn - Capture config file (default 'mls_rec_config.txt'). +% data_dir - Directory for the mls-.{wav,txt} outputs and the +% mls-ref.wav reference (default '.'). Created if missing. % % Output parameters % f - Frequency vector in Hz @@ -17,7 +22,8 @@ % mls_play_config.txt % mls_rec_config.txt % -% The script will return also a text CSV format file with name mls-.txt. +% The script will also write a text CSV format file with name mls-.txt +% and a PNG plot of the aligned response as mls-.png into data_dir. % % SPDX-License-Identifier: BSD-3-Clause @@ -26,24 +32,42 @@ % % Author: Seppo Ingalsuo +%% Octave dependencies: mlsp12/sync_chirp and the resampling path pull in +%% functions from the signal package. On most distros it ships separately +%% from core Octave, so load it explicitly rather than fail cryptically. +if exist('OCTAVE_VERSION', 'builtin') + pkg load signal; +end + %% Settings np = 1024; % Number of frequency points to use f_lo = 100; % Lower frequency limit for analysis f_hi = 20e3; % Upper frequency limit for analysis -t_tot = 10e-3; % MLS analysis window length in s +f_align_hz = 1000; % Response is normalized to 0 dB at this frequency +t_tot = 10e-3; % MLS analysis window length in s (480 samples @ 48 kHz) t_mls_s = 1.0; % MLS test signal length in s a_mls_db = -10; % MLS test signal amplitude in dB fs = 48e3; % Sample rate in Hz bits = 16; % Audio format to use (bits) fmt = 'S16_LE'; % Audio format to use (ALSA) -dir = '/tmp'; % Directory for temporary files +tmp_dir = '/tmp'; % Directory for temporary files capture_level_max_db = -1; % Expected max. level -capture_level_min_db = -30; % Expacted min. level +capture_level_min_db = -30; % Expected min. level %% Get device identifier to use if nargin < 1 id = 'unknown'; end +if nargin < 2 || isempty(play_config_fn) + play_config_fn = 'mls_play_config.txt'; +end +if nargin < 3 || isempty(rec_config_fn) + rec_config_fn = 'mls_rec_config.txt'; +end +if nargin < 4 || isempty(data_dir) + data_dir = '.'; +end +ensure_dir(data_dir); if strcmp(id, 'selftest') selftest = 1; @@ -57,16 +81,23 @@ else selftest = 0; end -measfn = sprintf('mls-%s.wav', id); -csvfn = sprintf('mls-%s.txt', id); +measfn = fullfile(data_dir, sprintf('mls-%s.wav', id)); +csvfn = fullfile(data_dir, sprintf('mls-%s.txt', id)); %% Paths -addpath('../../../../tools/test/audio/test_utils'); +% Resolve the test_utils dir from the script location so the script works +% independent of the caller's current working directory. onCleanup makes +% sure rmpath still runs if an error is raised later on. +script_dir = fileparts(mfilename('fullpath')); +test_utils_dir = fullfile(script_dir, '..', '..', '..', '..', ... + 'tools', 'test', 'audio', 'test_utils'); +addpath(test_utils_dir); +path_cleanup = onCleanup(@() rmpath(test_utils_dir)); %#ok %% MLS n_mls = round(fs*t_mls_s); mls = 10^(a_mls_db/20) * (2 * mlsp12(1, n_mls) - 1); -mlsfn = 'mls-ref.wav'; +mlsfn = fullfile(data_dir, 'mls-ref.wav'); audiowrite(mlsfn, mls, fs); %% Chip markers and parameters for find sync @@ -93,18 +124,20 @@ z(i2 + 1:end) = x2; %% Get config -rec_cfg = meas_remote_rec_config(fs, fmt); -play_cfg = meas_remote_play_config; +rec_cfg = meas_remote_rec_config(fs, fmt, rec_config_fn); +play_cfg = meas_remote_play_config(play_config_fn); %% Capture MLS from all playback channel at time mixfn = 'mlsmix.wav'; recfn = 'recch.wav'; -y = []; +nrec = play_cfg.nch * rec_cfg.nch; +y = []; % Preallocated once nt is known from the first find_test_signal +nt_expected = []; if selftest - labels = cell(play_cfg.nch * rec_cfg.nch + 1, 1); - labels(end) = 'Reference'; + labels = cell(nrec + 1, 1); + labels{end} = 'Reference'; else - labels = cell(play_cfg.nch * rec_cfg.nch, 1); + labels = cell(nrec, 1); end label_idx = 1; for i=1:play_cfg.nch @@ -119,7 +152,16 @@ else x = zeros(length(z), play_cfg.nch); x(:,i) = z; - mixdfn = sprintf('%s/%s', dir, mixfn); + % For an ssh play target we stage the WAV under tmp_dir and let + % copy_playback scp it over to play_cfg.dir. For a local play + % target copy_playback is a no-op, so write the WAV directly + % into play_cfg.dir where remote_play (which is local aplay in + % that case) will look for it. + if play_cfg.ssh + mixdfn = fullfile(tmp_dir, mixfn); + else + mixdfn = fullfile(play_cfg.dir, mixfn); + end audiowrite(mixdfn, x, fs, 'BitsPerSample', bits); copy_playback(mixdfn, play_cfg); tcap = floor(6 + t_mls_s); % Capture for MLS +6s @@ -130,7 +172,7 @@ r = get_recording(recfn, rec_cfg); end for j = 1:rec_cfg.nch - labels(label_idx) = sprintf('p%d-r%d', i, j); + labels{label_idx} = sprintf('p%d-r%d', i, j); label_idx = label_idx + 1; end [d, nt] = find_test_signal(r(:,1), fnd); @@ -148,11 +190,21 @@ m_db = []; return else - si = d:d + nt; + % nt is the sample count of the test signal, so the range + % highlighted here must be r(d:d+nt-1, :). + si = d:d + nt - 1; hold on - plot(ts(si), r(si), 'g'); + plot(ts(si), r(si, 1), 'g'); hold off end + if isempty(nt_expected) + nt_expected = nt; + y = zeros(nt, nrec); + elseif nt ~= nt_expected + error(['find_test_signal returned length %d on playback channel ' ... + '%d but %d on the first channel; captures are inconsistent'], ... + nt, i, nt_expected); + end for j = 1:rec_cfg.nch y(:, rec_cfg.nch*(i-1) + j) = r(d:d + nt -1, j); end @@ -178,8 +230,11 @@ [f, m_db] = apply_mic_calibration(f, m_db, rec_cfg); -figure -idx = find(f>1e3, 1, 'first') - 1; +main_fig = figure; +idx = find(f > f_align_hz, 1, 'first') - 1; +if isempty(idx) || idx < 1 + error('No frequency bin at or below %.0f Hz for alignment', f_align_hz); +end m_db_align = m_db - m_db(idx); semilogx(f, m_db_align); ax=axis(); axis([f_lo f_hi ax(3:4)]); @@ -195,10 +250,28 @@ hold on; plot(f, ref_db_align, 'r--'); hold off; +else + % Interpreter 'none' so underscores in the id are drawn literally + % instead of being taken as TeX subscript markers. + title(sprintf('Measured frequency response: %s', id), ... + 'Interpreter', 'none'); end legend(labels); +%% Save the plot as PNG next to the CSV so the response is easy to share +%% without pulling the raw capture through Octave again. print() picks +%% whichever graphics toolkit is active (qt on desktop, gnuplot in +%% --no-window-system runs), so both interactive and headless invocations +%% produce a file. +plot_fn = fullfile(data_dir, sprintf('mls-%s.png', id)); +try + print(main_fig, plot_fn, '-dpng', '-r150'); + fprintf('Wrote %s\n', plot_fn); +catch err + warning('Could not save %s: %s', plot_fn, err.message); +end + if selftest idx = find(f < f_hi); idx = find(f(idx) > f_lo); @@ -221,15 +294,16 @@ fprintf('Response RMS error is %4.1f dB.\n', e_db); end -rmpath('../../../../tools/test/audio/test_utils'); +% path_cleanup / onCleanup takes care of rmpath on both normal return and +% error paths. end function copy_playback(fn, cfg) if cfg.ssh - cmd = sprintf('scp %s %s:%s/', fn, cfg.user, cfg.dir); + cmd = sprintf('scp %s %s:%s/', shq(fn), cfg.user, shq(cfg.dir)); fprintf('Remote copy: %s\n', cmd); - system(cmd); + run_shell(cmd); else %cmd = sprintf('cp %s %s/', fn, cfg.dir); %fprintf('Local copy: %s\n', cmd); @@ -238,44 +312,83 @@ function copy_playback(fn, cfg) function y = get_recording(fn, cfg) if cfg.ssh - cmd = sprintf('scp %s:%s/%s %s', cfg.user, cfg.dir, fn, fn); + cmd = sprintf('scp %s:%s/%s %s', cfg.user, shq(cfg.dir), ... + shq(fn), shq(fn)); fprintf('Remote copy: %s\n', cmd); else - cmd = sprintf('cp %s/%s %s', cfg.dir, fn, fn); + cmd = sprintf('cp %s/%s %s', shq(cfg.dir), shq(fn), shq(fn)); fprintf('Local copy: %s\n', cmd); end - system(cmd); + run_shell(cmd); y = audioread(fn); delete(fn); end function remote_play(fn, cfg) if cfg.ssh - cmd = sprintf('ssh %s aplay -D%s %s/%s', cfg.user, cfg.dev, cfg.dir, fn); + cmd = sprintf('ssh %s aplay -D%s %s/%s', cfg.user, cfg.dev, ... + shq(cfg.dir), shq(fn)); fprintf('Remote play: %s\n', cmd); else - cmd = sprintf('aplay -D%s %s/%s', cfg.dev, cfg.dir, fn); + cmd = sprintf('aplay -D%s %s/%s', cfg.dev, shq(cfg.dir), shq(fn)); fprintf('Local play: %s\n', cmd); end - system(cmd); + run_shell(cmd); end function remote_capture(fn, cfg, t) if cfg.ssh cmd = sprintf('ssh %s arecord -q -D%s %s -d %d %s/%s &', ... - cfg.user, cfg.dev, cfg.fmt, t, cfg.dir, fn); + cfg.user, cfg.dev, cfg.fmt, t, ... + shq(cfg.dir), shq(fn)); fprintf('Remote capture: %s\n', cmd); else cmd = sprintf('arecord -q -D%s %s -d %d %s/%s &', ... - cfg.dev, cfg.fmt, t, cfg.dir, fn); + cfg.dev, cfg.fmt, t, shq(cfg.dir), shq(fn)); fprintf('Local capture: %s\n', cmd); end - system(cmd); + % Backgrounded capture: system() returns immediately with the shell's + % own exit code (0 for a successful fork), so a non-zero return here + % is a startup failure worth surfacing. + run_shell(cmd); end -function play = meas_remote_play_config() - play = get_config('mls_play_config.txt', 'play'); +function run_shell(cmd) +% Wrapper around system() that fails loudly instead of letting a broken +% aplay/arecord/scp step propagate as silent NaNs later on. + status = system(cmd); + if status ~= 0 + error('Shell command failed (status %d): %s', status, cmd); + end +end + +function q = shq(s) +% Wrap a string in POSIX single quotes for use in a shell command line, +% escaping any embedded single quotes. Cheap defense against paths that +% contain spaces or shell metacharacters. Any ' in s becomes the standard +% POSIX-shell '\'' close/escape/reopen sequence. + sq = char(39); % single quote + esc = [sq, '\', sq, sq]; % '\'' + if isempty(s) + q = [sq, sq]; + return; + end + q = [sq, strrep(s, sq, esc), sq]; +end + +function ensure_dir(d) + if ~exist(d, 'dir') + [ok, msg] = mkdir(d); + if ~ok + error('mkdir %s failed: %s', d, msg); + end + end +end + +function play = meas_remote_play_config(fn) + play = get_config(fn, 'play'); fprintf('\nThe settings for remote playback are\n'); + fprintf('Config : %s\n', fn); fprintf('Use ssh : %d\n', play.ssh); fprintf('User : %s\n', play.user); fprintf('Directory : %s\n', play.dir); @@ -283,12 +396,21 @@ function remote_capture(fn, cfg, t) fprintf('Channels : %d\n', play.nch); end -function rec = meas_remote_rec_config(fs, fmt) - rec = get_config('mls_rec_config.txt', 'rec'); +function rec = meas_remote_rec_config(fs, fmt, fn) + rec = get_config(fn, 'rec'); + % Let the config file override the ALSA format: some capture + % devices (e.g. SOF DMIC) only expose S32_LE, so the shell driver + % probes --dump-hw-params and writes rec.fmt_name / rec.bits in. + if isfield(rec, 'fmt_name') && ~isempty(rec.fmt_name) + fmt_use = rec.fmt_name; + else + fmt_use = fmt; + end rec.fmt = sprintf('-t wav -c %d -f %s -r %d', ... - rec.nch, fmt, fs); + rec.nch, fmt_use, fs); fprintf('\nThe settings for remote capture are\n'); + fprintf('Config : %s\n', fn); fprintf('Use ssh : %d\n', rec.ssh); fprintf('User : %s\n', rec.user); fprintf('Directory : %s\n', rec.dir); @@ -326,6 +448,7 @@ function remote_capture(fn, cfg, t) if fh < 0 error('Cannot open calibration data file'); end + fh_cleanup = onCleanup(@() fclose(fh)); %#ok n = 1; f = []; m = []; @@ -333,13 +456,13 @@ function remote_capture(fn, cfg, t) desc = ''; str = fgets(fh); idx = strfind(str, '"'); - while length(idx) > 0 + while ~isempty(idx) line = str(idx(1)+1:idx(2)-1); desc = sprintf('%s%s ', desc, line); str = fgets(fh); idx = strfind(str, '"'); end - if length(strfind(str, 'Sens')) + if ~isempty(strfind(str, 'Sens')) desc = str; str = fgets(fh); end @@ -352,7 +475,7 @@ function remote_capture(fn, cfg, t) end % Strip possible linefeed from description end - if double(desc(end)) == 10 + if ~isempty(desc) && double(desc(end)) == 10 desc = desc(1:end-1); end fprintf('Calibration Info : %s\n', desc); @@ -426,6 +549,11 @@ function remote_capture(fn, cfg, t) end function ret = get_config(fn, var) +% WARNING: this reads `fn` and executes it as Octave/MATLAB source via +% eval(). The config files (mls_play_config.txt / mls_rec_config.txt) are +% assumed to be locally owned and trusted; do not point this at anything +% you did not write yourself. Kept as-is for backward compatibility with +% existing user config files that rely on full script syntax. s = fileread(fn); eval(s); cmd = sprintf('ret = %s;', var); diff --git a/src/audio/eq_iir/tune/sof_mls_freq_resp_local.sh b/src/audio/eq_iir/tune/sof_mls_freq_resp_local.sh new file mode 100755 index 000000000000..eb03acce0a65 --- /dev/null +++ b/src/audio/eq_iir/tune/sof_mls_freq_resp_local.sh @@ -0,0 +1,883 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: BSD-3-Clause +# +# Copyright (c) 2026, Intel Corporation. +# +# sof_mls_freq_resp_local.sh +# +# Detect a suitable local ALSA speaker (aplay -l) + microphone +# (arecord -l, preferring a calibrated USB measurement mic), generate the +# mls_play_config.txt and mls_rec_config.txt files that sof_mls_freq_resp.m +# expects, and optionally run the Octave measurement into a data +# subdirectory. The id string is derived from DMI vendor + product so the +# output filenames identify the host that produced them. +# +# Typical use: +# ./sof_mls_freq_resp_local.sh # detect + write configs +# ./sof_mls_freq_resp_local.sh --run # + invoke Octave right away +# +# The Octave measurement can also be launched manually later with the +# printed command line. + +set -euo pipefail + +# -------------------------------------------------------------------------- +# Defaults & CLI parsing +# -------------------------------------------------------------------------- + +script_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) +data_dir="sof_mls_freq_resp_data" +id="" +id_auto=1 # 0 once --id STR was supplied by the user +append_mic_tag=1 # 0 with --no-mic-tag +speaker_override="" +mic_override="" +mic_channels="" # empty = auto-probe max from arecord +speaker_channels=2 +rec_fmt_override="" # non-empty with --rec-fmt to skip auto-probe +rec_cal="" # optional mic calibration file passed to rec.cal +do_run=0 +dry_run=0 +do_prep=0 +iir_pass_blob="$script_dir/../../../../tools/ctl/ipc4/eq_iir/pass.txt" +fir_pass_blob="$script_dir/../../../../tools/ctl/ipc4/eq_fir/pass.txt" + +usage() { + cat < S16_LE. + --cal PATH Path to a two-column [freq_hz, mag_db] microphone + calibration file. Written verbatim into rec.cal + so sof_mls_freq_resp.m compensates the measured + response with it. + --prep Before measuring, program pass-through IIR/FIR blobs + and switch DRC/MBDRC/TDFB controls off on the + speaker card so the response is not shaped by any + leftover DSP state. Uses amixer + sof-ctl. + --iir-blob PATH Override the IIR pass-through blob used by --prep. + (default: tools/ctl/ipc4/eq_iir/pass.txt) + --fir-blob PATH Override the FIR pass-through blob used by --prep. + (default: tools/ctl/ipc4/eq_fir/pass.txt) + --run Invoke Octave to run sof_mls_freq_resp() right after + writing the configs. + --dry-run Detect and print what would be written; do not touch + the filesystem or DSP controls. + -h, --help Show this help. +EOF +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --dir) data_dir="$2"; shift 2 ;; + --id) id="$2"; id_auto=0; shift 2 ;; + --no-mic-tag) append_mic_tag=0; shift ;; + --speaker) speaker_override="$2"; shift 2 ;; + --mic) mic_override="$2"; shift 2 ;; + --mic-ch) mic_channels="$2"; shift 2 ;; + --spk-ch) speaker_channels="$2"; shift 2 ;; + --rec-fmt) rec_fmt_override="$2"; shift 2 ;; + --cal) rec_cal="$2"; shift 2 ;; + --prep) do_prep=1; shift ;; + --iir-blob) iir_pass_blob="$2"; shift 2 ;; + --fir-blob) fir_pass_blob="$2"; shift 2 ;; + --run) do_run=1; shift ;; + --dry-run) dry_run=1; shift ;; + -h|--help) usage; exit 0 ;; + *) echo "Unknown option: $1" >&2; usage >&2; exit 2 ;; + esac +done + +# -------------------------------------------------------------------------- +# Dependencies +# -------------------------------------------------------------------------- + +need() { + command -v "$1" >/dev/null 2>&1 || { + echo "Required command '$1' not found in PATH" >&2 + exit 3 + } +} +need aplay +need arecord +need awk +# octave is only invoked when --run is given; skip the check in the +# default "generate configs only" mode so the script works on hosts +# without Octave installed. +if (( do_run )); then + need octave +fi + +extract_card() { + # "hw:0,0" -> "0". Used both by the prep step and by --dry-run. + echo "$1" | sed -n 's/^hw:\([0-9]\+\).*/\1/p' +} + +probe_capture_format() { + # Ask arecord which sample formats the device advertises via + # --dump-hw-params and pick one we know how to write in the rec + # config, preferring S32_LE over S16_LE (the DMIC PCM on many SOF + # platforms only exposes S32_LE). Falls back to S16_LE if the probe + # returns nothing usable (device busy, unknown output format) so + # the config file always has a definite value. + local dev="$1" formats want f + formats=$(arecord -D "$dev" --dump-hw-params 2>&1 \ + | sed -n 's/^FORMAT:[[:space:]]*//p' | head -1) + if [[ -n "$formats" ]]; then + for want in S32_LE S16_LE; do + for f in $formats; do + if [[ "$f" == "$want" ]]; then + echo "$want" + return 0 + fi + done + done + # Nothing on our preference list; take the first advertised. + echo "$formats" | awk '{print $1}' + return 0 + fi + echo "S16_LE" +} + +format_to_bits() { + case "$1" in + S32_LE|S32_BE|FLOAT_LE|FLOAT_BE) echo 32 ;; + S24_LE|S24_BE|S24_3LE|S24_3BE) echo 24 ;; + S16_LE|S16_BE) echo 16 ;; + *) echo 16 ;; + esac +} + +probe_capture_channels() { + # Return the max channel count arecord --dump-hw-params reports + # for the device, or empty string on failure. Handles both the + # single-value ("CHANNELS: 2") and range ("CHANNELS: [1 4]") forms. + local dev="$1" line n max=0 + line=$(arecord -D "$dev" --dump-hw-params 2>&1 \ + | sed -n 's/^CHANNELS:[[:space:]]*//p' | head -1) + [[ -z "$line" ]] && return 0 + line=${line//[/} + line=${line//]/} + for n in $line; do + if [[ "$n" =~ ^[0-9]+$ ]] && (( n > max )); then + max=$n + fi + done + (( max > 0 )) && echo "$max" +} + +# -------------------------------------------------------------------------- +# DMI-derived id +# -------------------------------------------------------------------------- + +dmi_read() { + # Read /sys/devices/virtual/dmi/id/, or empty on failure. + local key="$1" val="" + if [[ -r "/sys/devices/virtual/dmi/id/$key" ]]; then + val=$(cat "/sys/devices/virtual/dmi/id/$key" 2>/dev/null | tr -d '\n') + fi + printf '%s' "$val" +} + +sanitize_id() { + # Lower case, keep [a-z0-9], collapse everything else into a single _ + # and trim leading/trailing underscores. + echo "$1" | tr '[:upper:]' '[:lower:]' \ + | sed -e 's/[^a-z0-9]\+/_/g' -e 's/^_\+//' -e 's/_\+$//' +} + +# Read the raw DMI strings once so both the sanitized id below and the +# report at the end can show the exact values that ALSA UCM and the +# UCM blob generator expect (they must match /sys/devices/virtual/dmi/id +# verbatim, including case and whitespace). +dmi_vendor_raw=$(dmi_read sys_vendor) +dmi_product_raw=$(dmi_read product_name) + +if [[ -z "$id" ]]; then + vendor=$(sanitize_id "$dmi_vendor_raw") + product=$(sanitize_id "$dmi_product_raw") + if [[ -n "$vendor" && -n "$product" ]]; then + id="${vendor}_${product}" + elif [[ -n "$product" ]]; then + id="$product" + elif [[ -n "$vendor" ]]; then + id="$vendor" + else + id="unknown" + fi +fi + +# -------------------------------------------------------------------------- +# ALSA device discovery +# -------------------------------------------------------------------------- + +# Parse `aplay -l` / `arecord -l` lines of the form: +# card N: SHORT [LONG], device D: DESCR (...) [SUBNAME] +# Emits: "N|D|SHORT|LONG|DESCR" per PCM device. +# +# Pure POSIX-ERE sed (invoked with -E) instead of awk. gawk's 3-argument +# match() form is not portable to mawk (Ubuntu default /usr/bin/awk) or +# busybox awk (Alpine), so a sed-only extractor keeps the script working +# regardless of which awk implementation is installed. +parse_alsa_list() { + sed -nE ' + /^card [0-9]+: [^ ]+ \[[^]]+\], device [0-9]+: / { + # Rewrite the line as c|d|sn|ln|desc, then peel off the trailing + # " [SUBNAME]" and then the " (...)" annotation from desc. + # Bracket-strip has to run first so a line like + # ... device 0: Jack Out (*) [] + # does not leave the parenthesised part dangling at end-of-line. + s/^card ([0-9]+): ([^ ]+) \[([^]]+)\], device ([0-9]+): (.*)$/\1|\4|\2|\3|\5/ + s/ *\[[^]]*\] *$// + s/ *\([^)]*\) *$// + p + } + ' +} + +playback_devices=$(aplay -l 2>/dev/null | parse_alsa_list) +capture_devices=$(arecord -l 2>/dev/null | parse_alsa_list) + +if [[ -z "$playback_devices" ]]; then + echo "aplay -l returned no PCM devices" >&2 + exit 4 +fi +if [[ -z "$capture_devices" ]]; then + echo "arecord -l returned no PCM devices" >&2 + exit 4 +fi + +# -------------------------------------------------------------------------- +# Speaker selection +# -------------------------------------------------------------------------- +# +# Preference order: +# 1) Any PCM whose descriptor contains "Speaker" (common on SOF+SDW, +# where the aplay -l descriptors are literal endpoint names such as +# "Jack Out", "Speaker", "HDMI1", "Deepbuffer Jack Out"). This has +# to run before the analog rules because SDW cards do not advertise +# "Analog" in their descriptors and would otherwise fall through to +# the "first non-HDMI/DP" pass and pick "Jack Out". +# 2) SOF card ("sof*" short name) with an "Analog" descriptor that is +# not "Deepbuffer". +# 3) Any card device whose descriptor contains "Analog" but not "HDMI", +# "DP" or "Deepbuffer". +# 4) First non-HDMI/DP/Deepbuffer device. +# HDMI/DP outputs are always skipped so the mic never accidentally captures +# monitor speaker output. + +pick_speaker() { + local line c d sn ln desc lc_ln lc_desc + + # Pass 1: descriptor explicitly named "Speaker" + while IFS='|' read -r c d sn ln desc; do + [[ -z "$c" ]] && continue + lc_desc=$(echo "$desc" | tr '[:upper:]' '[:lower:]') + if [[ "$lc_desc" == *speaker* ]] \ + && [[ "$lc_desc" != *hdmi* ]] \ + && [[ "$lc_desc" != *dp* ]]; then + echo "hw:$c,$d|$sn|$ln|$desc|speaker" + return + fi + done <<<"$playback_devices" + + # Pass 2: SOF analog + while IFS='|' read -r c d sn ln desc; do + [[ -z "$c" ]] && continue + lc_desc=$(echo "$desc" | tr '[:upper:]' '[:lower:]') + lc_ln=$(echo "$ln" | tr '[:upper:]' '[:lower:]') + if [[ "$lc_ln" == sof* ]] \ + && [[ "$lc_desc" == *analog* ]] \ + && [[ "$lc_desc" != *deepbuffer* ]] \ + && [[ "$lc_desc" != *hdmi* ]] \ + && [[ "$lc_desc" != *dp* ]]; then + echo "hw:$c,$d|$sn|$ln|$desc|sof-analog" + return + fi + done <<<"$playback_devices" + + # Pass 3: any analog, still skipping HDMI, DP and Deepbuffer. + while IFS='|' read -r c d sn ln desc; do + [[ -z "$c" ]] && continue + lc_desc=$(echo "$desc" | tr '[:upper:]' '[:lower:]') + if [[ "$lc_desc" == *analog* ]] \ + && [[ "$lc_desc" != *deepbuffer* ]] \ + && [[ "$lc_desc" != *hdmi* ]] \ + && [[ "$lc_desc" != *dp* ]]; then + echo "hw:$c,$d|$sn|$ln|$desc|analog" + return + fi + done <<<"$playback_devices" + + # Pass 4: first non-hdmi/dp/deepbuffer + while IFS='|' read -r c d sn ln desc; do + [[ -z "$c" ]] && continue + lc_desc=$(echo "$desc" | tr '[:upper:]' '[:lower:]') + if [[ "$lc_desc" != *hdmi* ]] \ + && [[ "$lc_desc" != *dp* ]] \ + && [[ "$lc_desc" != *deepbuffer* ]]; then + echo "hw:$c,$d|$sn|$ln|$desc|first-non-hdmi" + return + fi + done <<<"$playback_devices" + + return 1 +} + +# -------------------------------------------------------------------------- +# Microphone selection +# -------------------------------------------------------------------------- +# +# Preference order: +# 1) A known calibrated measurement mic by long-name substring +# (UMM-6, UMIK, MiniDSP, EMM-6, ECM8000). +# 2) Any USB-Audio-class card that is not obviously a webcam or a full +# interface (best-effort heuristic on the long name). +# 3) A SOF DMIC device (internal digital mic array). +# 4) The SOF analog line-in / any first analog capture. + +is_measurement_mic() { + local lc="$1" + case "$lc" in + *umm-6*|*umm6*|*umik*|*minidsp*|*emm-6*|*emm6*|*ecm8000*) + return 0 ;; + *) return 1 ;; + esac +} + +is_webcam() { + local lc="$1" + case "$lc" in + *webcam*|*c920*|*c930*|*brio*|*logitech*camera*|*camera*) + return 0 ;; + *) return 1 ;; + esac +} + +pick_mic() { + local line c d sn ln desc lc_ln lc_desc lc_sn + + # Pass 1: known measurement mics + while IFS='|' read -r c d sn ln desc; do + [[ -z "$c" ]] && continue + lc_ln=$(echo "$ln" | tr '[:upper:]' '[:lower:]') + lc_sn=$(echo "$sn" | tr '[:upper:]' '[:lower:]') + if is_measurement_mic "$lc_ln" || is_measurement_mic "$lc_sn"; then + echo "hw:$c,$d|$sn|$ln|$desc|measurement-usb" + return + fi + done <<<"$capture_devices" + + # Pass 2: generic USB audio card, not a webcam + while IFS='|' read -r c d sn ln desc; do + [[ -z "$c" ]] && continue + lc_ln=$(echo "$ln" | tr '[:upper:]' '[:lower:]') + lc_desc=$(echo "$desc" | tr '[:upper:]' '[:lower:]') + lc_sn=$(echo "$sn" | tr '[:upper:]' '[:lower:]') + if [[ "$lc_sn" == usb* || "$lc_desc" == *usb* ]] \ + && ! is_webcam "$lc_ln"; then + echo "hw:$c,$d|$sn|$ln|$desc|usb-generic" + return + fi + done <<<"$capture_devices" + + # Pass 3: SOF DMIC (built-in digital mic array) + while IFS='|' read -r c d sn ln desc; do + [[ -z "$c" ]] && continue + lc_ln=$(echo "$ln" | tr '[:upper:]' '[:lower:]') + lc_desc=$(echo "$desc" | tr '[:upper:]' '[:lower:]') + if [[ "$lc_ln" == sof* ]] && [[ "$lc_desc" == *dmic* ]]; then + echo "hw:$c,$d|$sn|$ln|$desc|sof-dmic" + return + fi + done <<<"$capture_devices" + + # Pass 4: SOF / first analog capture + while IFS='|' read -r c d sn ln desc; do + [[ -z "$c" ]] && continue + lc_desc=$(echo "$desc" | tr '[:upper:]' '[:lower:]') + if [[ "$lc_desc" == *analog* ]]; then + echo "hw:$c,$d|$sn|$ln|$desc|analog-capture" + return + fi + done <<<"$capture_devices" + + # Pass 5: give up and take the very first entry + IFS='|' read -r c d sn ln desc <<<"$(echo "$capture_devices" | head -n 1)" + echo "hw:$c,$d|$sn|$ln|$desc|first-listed" +} + +# -------------------------------------------------------------------------- +# Resolve devices +# -------------------------------------------------------------------------- + +if [[ -n "$speaker_override" ]]; then + speaker_hw="$speaker_override" + speaker_info="override" + speaker_name="$speaker_override" +else + IFS='|' read -r speaker_hw _spk_sn speaker_name _spk_desc speaker_info \ + <<<"$(pick_speaker || true)" + if [[ -z "${speaker_hw:-}" ]]; then + echo "Could not identify a suitable speaker device from aplay -l" >&2 + aplay -l >&2 + exit 5 + fi +fi + +if [[ -n "$mic_override" ]]; then + mic_hw="$mic_override" + mic_info="override" + # Try to look the override up in the capture list so the mic tag is + # still descriptive (e.g. dmic_raw) instead of just the hw address. + ov_card=$(extract_card "$mic_override") + ov_dev=$(echo "$mic_override" | sed -n 's/^hw:[0-9]\+,\([0-9]\+\).*/\1/p') + mic_name="$mic_override" + mic_desc="$mic_override" + if [[ -n "$ov_card" && -n "$ov_dev" ]]; then + while IFS='|' read -r c d _sn ln desc; do + if [[ "$c" == "$ov_card" && "$d" == "$ov_dev" ]]; then + mic_name="$ln" + mic_desc="$desc" + break + fi + done <<<"$capture_devices" + fi +else + IFS='|' read -r mic_hw _mic_sn mic_name mic_desc mic_info \ + <<<"$(pick_mic)" + if [[ -z "${mic_hw:-}" ]]; then + echo "Could not identify any capture device from arecord -l" >&2 + arecord -l >&2 + exit 5 + fi +fi + +# -------------------------------------------------------------------------- +# Microphone tag for the id +# -------------------------------------------------------------------------- +# +# For USB / measurement mics the long name ("MiniDSP UMM-6", "HD Pro +# Webcam C920") is the most identifiable label. For the SOF card the +# long name collapses to "sof-hda-dsp" for every capture PCM on the +# card, so fall back to the PCM descriptor ("DMIC Raw", "HDA Analog") +# to distinguish e.g. the DMIC array from a wired line-in. If both are +# generic, fall back to the hw:C,D address so we always get something +# unique. + +mic_tag_from() { + local ln="$1" desc="$2" hw="$3" base lc_ln + lc_ln=$(printf '%s' "$ln" | tr '[:upper:]' '[:lower:]') + if [[ -n "$ln" ]] && [[ "$lc_ln" != sof* ]] && [[ "$lc_ln" != hda* ]]; then + base="$ln" + elif [[ -n "$desc" ]]; then + base="$desc" + else + base="$hw" + fi + sanitize_id "$base" +} + +mic_tag=$(mic_tag_from "$mic_name" "$mic_desc" "$mic_hw") +if (( id_auto )) && (( append_mic_tag )) && [[ -n "$mic_tag" ]]; then + id="${id}_${mic_tag}" +fi + +# -------------------------------------------------------------------------- +# Avoid overwriting a previous measurement +# -------------------------------------------------------------------------- +# +# The Octave side writes mls-.{wav,txt,png} into $data_dir. If any of +# them already exists (typical when re-measuring the same box at a new mic +# location), suffix the id with -2, -3, ... until nothing would collide. +# Only applied when the id was auto-derived; a user-supplied --id is +# honored verbatim so scripted overwrites still work. +uniquify_id() { + local base="$1" candidate="$1" n=2 + while [[ -e "$data_dir/mls-${candidate}.wav" \ + || -e "$data_dir/mls-${candidate}.txt" \ + || -e "$data_dir/mls-${candidate}.png" ]]; do + candidate="${base}-${n}" + n=$(( n + 1 )) + done + printf '%s' "$candidate" +} + +if (( id_auto )); then + new_id=$(uniquify_id "$id") + if [[ "$new_id" != "$id" ]]; then + echo "Note: prior measurement for '$id' found in $data_dir;" \ + "using id '$new_id' to avoid overwrite" >&2 + id="$new_id" + fi +fi + +# Resolve the ALSA capture format. Honor --rec-fmt if the user forced +# one; otherwise probe the mic and prefer S32_LE over S16_LE. +if [[ -n "$rec_fmt_override" ]]; then + rec_fmt="$rec_fmt_override" +else + rec_fmt=$(probe_capture_format "$mic_hw") +fi +rec_bits=$(format_to_bits "$rec_fmt") + +# Resolve the capture channel count similarly: if --mic-ch was given, +# use it verbatim; otherwise probe the max the mic advertises. Fall +# back to 1 when the probe returns nothing (device busy, etc.). +if [[ -z "$mic_channels" ]]; then + probed_ch=$(probe_capture_channels "$mic_hw") + if [[ -n "$probed_ch" ]]; then + mic_channels="$probed_ch" + else + mic_channels=1 + echo "Note: could not probe channel count for $mic_hw, using 1" >&2 + fi +fi + +# -------------------------------------------------------------------------- +# Report + write configs +# -------------------------------------------------------------------------- + +echo "DMI vendor : ${dmi_vendor_raw:-}" +echo "DMI product : ${dmi_product_raw:-}" +echo "Mic tag : ${mic_tag:-}" +if (( id_auto )); then + echo "DMI id : $id (sanitized, used in blob file names)" +else + echo "DMI id : $id (user-supplied via --id, used verbatim)" +fi +echo "Speaker : $speaker_hw ($speaker_name, $speaker_info)" +echo "Microphone : $mic_hw ($mic_name, $mic_info)" +echo "Rec format : $rec_fmt (${rec_bits}-bit)" +echo "Mic cal file : ${rec_cal:-}" +echo "Data dir : $data_dir" +echo "Speaker chs : $speaker_channels" +echo "Mic chs : $mic_channels" + +if [[ -n "$rec_cal" ]] && [[ ! -f "$rec_cal" ]]; then + echo "Warning: calibration file '$rec_cal' does not exist;" \ + "sof_mls_freq_resp.m will error out." >&2 +fi + +if is_webcam "$(echo "$mic_name" | tr '[:upper:]' '[:lower:]')"; then + echo "Warning: selected mic looks like a webcam; measurements are unlikely" \ + "to be usable. Consider passing --mic hw:C,D explicitly." >&2 +fi +# Base the calibrated-mic note on the actual device name/description so that +# a user forcing --mic hw:C,D on a real UMM-6 does not get a misleading +# "no calibrated mic" warning. mic_info can be "override" in that case, so +# it is not a reliable signal on its own. +lc_mic_name=$(echo "$mic_name" | tr '[:upper:]' '[:lower:]') +lc_mic_desc=$(echo "$mic_desc" | tr '[:upper:]' '[:lower:]') +if ! is_measurement_mic "$lc_mic_name" && ! is_measurement_mic "$lc_mic_desc"; then + echo "Note: no calibrated measurement microphone (UMM-6/UMIK/MiniDSP)" \ + "detected; results will not be calibrated." >&2 +fi + +play_conf="$data_dir/mls_play_config.txt" +rec_conf="$data_dir/mls_rec_config.txt" + +if (( dry_run )); then + echo + echo "-- Would write $play_conf --" + echo "-- Would write $rec_conf --" + if (( do_prep )); then + echo "-- Would prep card $(extract_card "$speaker_hw"):" + echo " IIR blob = $iir_pass_blob" + echo " FIR blob = $fir_pass_blob" + echo " DRC/MBDRC/TDFB switches -> off" + fi + exit 0 +fi + +mkdir -p -- "$data_dir" + +cat > "$play_conf" < "$rec_conf" <&2 + return 1 + fi + if [[ ! -f "$fir_blob" ]]; then + echo "FIR pass-through blob not found: $fir_blob" >&2 + return 1 + fi + + need amixer + need sof-ctl + need timeout + + echo + echo "Prep: card=$ctldev" + echo " IIR pass blob = $iir_blob" + echo " FIR pass blob = $fir_blob" + + # probe_bytes_size + # + # Return the on-device max byte-buffer size of the given BYTES + # control by asking sof-ctl to read it (no -s) and parsing the + # "Control size is N." line sof-ctl prints on stdout. amixer cannot + # read large bytes controls (they use the TLV path), so sof-ctl is + # the only usable probe. + # + # sof-ctl CLI: `-c name='...'` looks up the control by name; this + # is essential because `-n numid=N` for the same control on some + # SOF topologies returns "Control size is 1." (wrong max), for the + # same reason amixer's numid= path is buggy on these controls. + # `-i 4` is intentionally omitted here — we are only reading and + # do not care about the ABI type for size reporting. + # + # Empty on any failure (sof-ctl missing, control not found, ioctl + # error, timeout). + probe_bytes_size() { + local card="$1" name="$2" out + out=$(timeout 5 sof-ctl -D "hw:$card" -c name="$name" 2>&1) \ + || true + printf '%s\n' "$out" \ + | sed -nE 's/^Control size is ([0-9]+)\..*/\1/p' \ + | head -1 + } + + # probe_type_desc + # + # Return a short human-readable diagnostic line from sof-ctl's + # combined stderr+stdout, used only for the skip message. Prefers + # the "Control size is N." line, then a "hdr: magic ..." line, + # then any error-looking line, else the first line. Empty on total + # probe failure. + probe_type_desc() { + local card="$1" name="$2" out line + out=$(timeout 5 sof-ctl -D "hw:$card" -c name="$name" 2>&1) \ + || true + line=$(printf '%s\n' "$out" | grep -E '^Control size is ' | head -1) + if [[ -z "$line" ]]; then + line=$(printf '%s\n' "$out" | grep -E '^hdr: ' | head -1) + fi + if [[ -z "$line" ]]; then + line=$(printf '%s\n' "$out" | grep -Ei 'error|failed|not ' | head -1) + fi + if [[ -z "$line" ]]; then + line=$(printf '%s\n' "$out" | head -1) + fi + printf '%s' "$line" + } + + # Blob writes below the minimum size are refused; the SOF pass- + # through IIR blob is a few hundred bytes and FIR is >= 1 KiB, so + # anything under 64 bytes is definitely not a real EQ buffer. + local min_blob_bytes=64 + + # Snapshot the matching controls into an array so the loop body + # runs in the parent shell (not a pipeline subshell). This makes + # any hang or failure inside the body visible instead of silently + # terminating the subshell mid-loop. The grep filter is wrapped in + # `{ ... || true; }` so that a card without any IIR/FIR/DRC/MBDRC/ + # TDFB controls does not trip `set -euo pipefail`. + local ctl_lines line numid name size rc reason + mapfile -t ctl_lines < <(amixer -c "$card" controls \ + | { grep -E "IIR|FIR|DRC|MBDRC|TDFB" || true; }) + + if (( ${#ctl_lines[@]} == 0 )); then + echo " (no IIR/FIR/DRC/MBDRC/TDFB controls found on card $card)" + return 0 + fi + echo " ${#ctl_lines[@]} matching control(s) to process" + + for line in "${ctl_lines[@]}"; do + numid=$(printf '%s\n' "$line" \ + | sed -n 's/^numid=\([0-9]\+\),.*/\1/p') + name=$(printf '%s\n' "$line" \ + | sed -n "s/.*name='\\(.*\\)'[[:space:]]*$/\\1/p") + [[ -z "$numid" || -z "$name" ]] && continue + + case "$name" in + # Switches: DRC / MBDRC / TDFB -> off + *[Dd][Rr][Cc]*switch \ + | *[Tt][Dd][Ff][Bb]*switch \ + | *[Mm][Bb][Dd][Rr][Cc]*switch) + printf ' switch off numid=%s %s\n' \ + "$numid" "$name" + # amixer >= 1.2.13 has a known assertion + # failure ("info->id.name[0] || info->id.numid") + # in snd_ctl_elem_info() when cset is invoked + # with numid= for some switch controls; the + # process aborts with a core dump. Use the + # iface=MIXER,name='...' form instead, which + # takes the name-lookup path and sidesteps the + # buggy branch. `timeout` guards against a + # hung ioctl (e.g. DSP not responding). + rc=0 + timeout 5 amixer -q -c "$card" cset \ + iface=MIXER,name="$name" off || rc=$? + if (( rc != 0 )); then + echo " ...amixer failed (exit=$rc)" >&2 + fi + ;; + # IIR bytes / IIR Eq -> pass-through + *[Ii][Ii][Rr]*[Bb]ytes \ + | *[Ii][Ii][Rr]" Eq") + # `|| size=""` is REQUIRED here: under `set -e`, + # an assignment `x=$(cmd)` where the command + # substitution's pipeline fails (e.g. sof-ctl + # returns non-zero, and pipefail propagates it) + # will silently kill the entire script. Neutralise + # it so probe_bytes_size is treated as best-effort. + size=$(probe_bytes_size "$card" "$name") || size="" + if [[ -z "$size" ]]; then + reason=$(probe_type_desc "$card" "$name") + printf ' iir skip numid=%s %s (%s)\n' \ + "$numid" "$name" "${reason:-sof-ctl probe failed}" + continue + fi + if (( size < min_blob_bytes )); then + printf ' iir skip numid=%s %s (bytes size=%s, not a real EQ buffer)\n' \ + "$numid" "$name" "$size" + continue + fi + printf ' iir pass numid=%s %s (%s bytes)\n' \ + "$numid" "$name" "$size" + rc=0 + # Set via -c name= for the same reason the probe + # does: -n numid= can report wrong max sizes and + # is prone to the same buggy code path amixer + # hits with numid= access. + timeout 5 sof-ctl -i 4 -D "$ctldev" -c name="$name" \ + -s "$iir_blob" >/dev/null || rc=$? + if (( rc != 0 )); then + echo " ...sof-ctl failed (exit=$rc)" >&2 + fi + ;; + # FIR bytes / FIR Eq -> pass-through + *[Ff][Ii][Rr]*[Bb]ytes \ + | *[Ff][Ii][Rr]" Eq") + size=$(probe_bytes_size "$card" "$name") || size="" + if [[ -z "$size" ]]; then + reason=$(probe_type_desc "$card" "$name") + printf ' fir skip numid=%s %s (%s)\n' \ + "$numid" "$name" "${reason:-sof-ctl probe failed}" + continue + fi + if (( size < min_blob_bytes )); then + printf ' fir skip numid=%s %s (bytes size=%s, not a real EQ buffer)\n' \ + "$numid" "$name" "$size" + continue + fi + printf ' fir pass numid=%s %s (%s bytes)\n' \ + "$numid" "$name" "$size" + rc=0 + timeout 5 sof-ctl -i 4 -D "$ctldev" -c name="$name" \ + -s "$fir_blob" >/dev/null || rc=$? + if (( rc != 0 )); then + echo " ...sof-ctl failed (exit=$rc)" >&2 + fi + ;; + # Bytes controls we don't have a canonical pass-through + # for are just reported so the user sees what was left + # alone. + *[Bb]ytes|*enum|*[Ee]num) + printf ' skipped numid=%s %s\n' \ + "$numid" "$name" + ;; + esac + done +} + +if (( do_prep )); then + spk_card=$(extract_card "$speaker_hw") + if [[ -z "$spk_card" ]]; then + echo "Cannot extract card number from speaker '$speaker_hw'" >&2 + exit 6 + fi + prep_controls_passthrough "$spk_card" \ + "$iir_pass_blob" "$fir_pass_blob" +fi + +# -------------------------------------------------------------------------- +# Optionally invoke Octave +# -------------------------------------------------------------------------- + +# Escape single quotes so they survive being embedded in an Octave +# single-quoted string literal (the doubling rule Octave uses for ''). +# In practice id is sanitized to [a-z0-9_] and the paths come from our +# own $TMPDIR / script location, but a --dir or --id with a stray ' in +# it would otherwise break the string or inject additional Octave code. +oct_squote() { + printf "%s" "${1//\'/\'\'}" +} + +q_script_dir=$(oct_squote "$script_dir") +q_id=$(oct_squote "$id") +q_play_conf=$(oct_squote "$play_conf") +q_rec_conf=$(oct_squote "$rec_conf") +q_data_dir=$(oct_squote "$data_dir") + +octave_eval="cd('$q_script_dir'); sof_mls_freq_resp('$q_id', '$q_play_conf', '$q_rec_conf', '$q_data_dir');" + +echo +echo "To run the measurement manually:" +echo " octave --no-gui --no-window-system --eval \"$octave_eval\"" + +if (( do_run )); then + echo + echo "Running measurement..." + octave --no-gui --no-window-system --eval "$octave_eval" +fi diff --git a/src/audio/eq_iir/tune/sof_ucm2_eq_example.txt b/src/audio/eq_iir/tune/sof_ucm2_eq_example.txt new file mode 100644 index 000000000000..1b47705f08bd --- /dev/null +++ b/src/audio/eq_iir/tune/sof_ucm2_eq_example.txt @@ -0,0 +1,1024 @@ + 100.000, -32.508, -34.491 + 100.519, -32.455, -34.372 + 101.041, -32.402, -34.252 + 101.566, -32.349, -34.130 + 102.093, -32.296, -34.008 + 102.623, -32.242, -33.885 + 103.156, -32.189, -33.761 + 103.692, -32.135, -33.636 + 104.230, -32.080, -33.512 + 104.772, -32.026, -33.387 + 105.316, -31.971, -33.261 + 105.863, -31.916, -33.136 + 106.412, -31.861, -33.011 + 106.965, -31.806, -32.885 + 107.520, -31.750, -32.760 + 108.079, -31.694, -32.635 + 108.640, -31.638, -32.510 + 109.204, -31.581, -32.386 + 109.771, -31.525, -32.262 + 110.341, -31.468, -32.138 + 110.914, -31.411, -32.015 + 111.490, -31.353, -31.893 + 112.069, -31.295, -31.771 + 112.651, -31.237, -31.649 + 113.236, -31.179, -31.528 + 113.824, -31.120, -31.407 + 114.415, -31.061, -31.288 + 115.009, -31.001, -31.168 + 115.606, -30.941, -31.050 + 116.206, -30.881, -30.932 + 116.810, -30.820, -30.814 + 117.416, -30.759, -30.698 + 118.026, -30.698, -30.582 + 118.639, -30.636, -30.466 + 119.255, -30.573, -30.351 + 119.874, -30.510, -30.237 + 120.497, -30.447, -30.124 + 121.122, -30.383, -30.011 + 121.751, -30.318, -29.899 + 122.383, -30.253, -29.787 + 123.019, -30.188, -29.676 + 123.658, -30.121, -29.565 + 124.300, -30.055, -29.456 + 124.945, -29.987, -29.346 + 125.594, -29.919, -29.238 + 126.246, -29.850, -29.129 + 126.902, -29.781, -29.022 + 127.561, -29.711, -28.915 + 128.223, -29.640, -28.808 + 128.889, -29.568, -28.702 + 129.558, -29.496, -28.596 + 130.231, -29.423, -28.491 + 130.907, -29.349, -28.387 + 131.587, -29.274, -28.283 + 132.270, -29.198, -28.179 + 132.957, -29.122, -28.076 + 133.647, -29.045, -27.973 + 134.341, -28.967, -27.870 + 135.039, -28.888, -27.768 + 135.740, -28.808, -27.666 + 136.445, -28.727, -27.565 + 137.154, -28.646, -27.464 + 137.866, -28.563, -27.363 + 138.582, -28.480, -27.263 + 139.301, -28.395, -27.163 + 140.025, -28.310, -27.063 + 140.752, -28.224, -26.963 + 141.483, -28.137, -26.864 + 142.217, -28.048, -26.765 + 142.956, -27.959, -26.666 + 143.698, -27.869, -26.567 + 144.444, -27.778, -26.468 + 145.194, -27.686, -26.370 + 145.948, -27.593, -26.272 + 146.706, -27.499, -26.174 + 147.468, -27.404, -26.076 + 148.234, -27.308, -25.978 + 149.003, -27.211, -25.880 + 149.777, -27.114, -25.782 + 150.555, -27.015, -25.685 + 151.337, -26.915, -25.587 + 152.122, -26.815, -25.489 + 152.912, -26.713, -25.392 + 153.706, -26.611, -25.294 + 154.504, -26.508, -25.196 + 155.307, -26.403, -25.099 + 156.113, -26.298, -25.001 + 156.924, -26.193, -24.903 + 157.739, -26.086, -24.805 + 158.558, -25.978, -24.707 + 159.381, -25.870, -24.609 + 160.209, -25.761, -24.511 + 161.041, -25.651, -24.412 + 161.877, -25.540, -24.314 + 162.717, -25.429, -24.215 + 163.562, -25.317, -24.116 + 164.412, -25.204, -24.017 + 165.265, -25.091, -23.918 + 166.123, -24.977, -23.818 + 166.986, -24.862, -23.718 + 167.853, -24.747, -23.618 + 168.725, -24.631, -23.518 + 169.601, -24.515, -23.417 + 170.482, -24.398, -23.316 + 171.367, -24.281, -23.215 + 172.257, -24.163, -23.113 + 173.151, -24.045, -23.012 + 174.050, -23.926, -22.909 + 174.954, -23.807, -22.807 + 175.863, -23.687, -22.704 + 176.776, -23.568, -22.601 + 177.694, -23.448, -22.497 + 178.616, -23.327, -22.393 + 179.544, -23.206, -22.289 + 180.476, -23.086, -22.184 + 181.413, -22.964, -22.079 + 182.355, -22.843, -21.973 + 183.302, -22.722, -21.867 + 184.254, -22.600, -21.761 + 185.211, -22.478, -21.654 + 186.173, -22.356, -21.547 + 187.139, -22.234, -21.439 + 188.111, -22.112, -21.331 + 189.088, -21.990, -21.223 + 190.070, -21.868, -21.114 + 191.057, -21.746, -21.005 + 192.049, -21.624, -20.895 + 193.046, -21.502, -20.785 + 194.048, -21.381, -20.675 + 195.056, -21.259, -20.565 + 196.069, -21.137, -20.453 + 197.087, -21.016, -20.342 + 198.110, -20.895, -20.230 + 199.139, -20.773, -20.118 + 200.173, -20.652, -20.006 + 201.213, -20.532, -19.893 + 202.257, -20.411, -19.780 + 203.308, -20.291, -19.667 + 204.363, -20.171, -19.554 + 205.424, -20.051, -19.440 + 206.491, -19.932, -19.326 + 207.563, -19.812, -19.212 + 208.641, -19.693, -19.097 + 209.725, -19.575, -18.983 + 210.814, -19.457, -18.868 + 211.908, -19.339, -18.753 + 213.009, -19.221, -18.638 + 214.115, -19.104, -18.523 + 215.227, -18.987, -18.408 + 216.344, -18.870, -18.293 + 217.468, -18.754, -18.178 + 218.597, -18.639, -18.063 + 219.732, -18.523, -17.947 + 220.873, -18.408, -17.832 + 222.020, -18.294, -17.717 + 223.173, -18.180, -17.603 + 224.331, -18.066, -17.488 + 225.496, -17.953, -17.373 + 226.667, -17.840, -17.259 + 227.844, -17.727, -17.145 + 229.027, -17.616, -17.031 + 230.217, -17.504, -16.918 + 231.412, -17.393, -16.804 + 232.614, -17.283, -16.691 + 233.822, -17.172, -16.579 + 235.036, -17.063, -16.467 + 236.256, -16.954, -16.355 + 237.483, -16.845, -16.244 + 238.716, -16.737, -16.133 + 239.956, -16.630, -16.023 + 241.202, -16.523, -15.914 + 242.454, -16.416, -15.805 + 243.713, -16.310, -15.696 + 244.979, -16.205, -15.588 + 246.251, -16.100, -15.481 + 247.529, -15.996, -15.375 + 248.815, -15.892, -15.269 + 250.107, -15.790, -15.164 + 251.405, -15.687, -15.060 + 252.711, -15.585, -14.957 + 254.023, -15.484, -14.854 + 255.342, -15.384, -14.753 + 256.668, -15.284, -14.652 + 258.001, -15.185, -14.552 + 259.341, -15.086, -14.453 + 260.687, -14.989, -14.355 + 262.041, -14.891, -14.258 + 263.402, -14.795, -14.162 + 264.769, -14.699, -14.067 + 266.144, -14.604, -13.973 + 267.526, -14.510, -13.880 + 268.915, -14.416, -13.788 + 270.312, -14.324, -13.697 + 271.715, -14.232, -13.607 + 273.126, -14.140, -13.518 + 274.545, -14.049, -13.430 + 275.970, -13.959, -13.344 + 277.403, -13.870, -13.258 + 278.844, -13.781, -13.174 + 280.292, -13.693, -13.090 + 281.747, -13.606, -13.008 + 283.210, -13.519, -12.926 + 284.681, -13.433, -12.846 + 286.159, -13.348, -12.767 + 287.645, -13.263, -12.688 + 289.138, -13.178, -12.611 + 290.640, -13.094, -12.535 + 292.149, -13.011, -12.459 + 293.666, -12.927, -12.385 + 295.191, -12.844, -12.311 + 296.724, -12.762, -12.238 + 298.264, -12.679, -12.166 + 299.813, -12.597, -12.095 + 301.370, -12.514, -12.024 + 302.935, -12.432, -11.955 + 304.508, -12.350, -11.885 + 306.089, -12.267, -11.817 + 307.679, -12.184, -11.748 + 309.276, -12.101, -11.681 + 310.882, -12.018, -11.613 + 312.497, -11.934, -11.546 + 314.119, -11.849, -11.479 + 315.750, -11.764, -11.412 + 317.390, -11.678, -11.346 + 319.038, -11.591, -11.279 + 320.695, -11.503, -11.212 + 322.360, -11.414, -11.145 + 324.034, -11.324, -11.078 + 325.716, -11.232, -11.010 + 327.408, -11.139, -10.942 + 329.108, -11.045, -10.874 + 330.817, -10.950, -10.805 + 332.535, -10.852, -10.735 + 334.261, -10.753, -10.664 + 335.997, -10.653, -10.592 + 337.742, -10.550, -10.519 + 339.495, -10.446, -10.446 + 341.258, -10.340, -10.370 + 343.030, -10.232, -10.294 + 344.812, -10.122, -10.216 + 346.602, -10.010, -10.137 + 348.402, -9.896, -10.057 + 350.211, -9.781, -9.974 + 352.029, -9.663, -9.890 + 353.857, -9.543, -9.804 + 355.695, -9.422, -9.717 + 357.542, -9.298, -9.627 + 359.398, -9.173, -9.535 + 361.265, -9.047, -9.442 + 363.141, -8.918, -9.346 + 365.026, -8.788, -9.249 + 366.922, -8.657, -9.149 + 368.827, -8.524, -9.048 + 370.742, -8.391, -8.944 + 372.667, -8.256, -8.838 + 374.602, -8.120, -8.730 + 376.548, -7.983, -8.620 + 378.503, -7.845, -8.508 + 380.468, -7.707, -8.394 + 382.444, -7.569, -8.278 + 384.430, -7.430, -8.160 + 386.426, -7.291, -8.040 + 388.433, -7.152, -7.918 + 390.450, -7.013, -7.794 + 392.477, -6.874, -7.669 + 394.515, -6.736, -7.543 + 396.564, -6.599, -7.414 + 398.623, -6.462, -7.284 + 400.693, -6.326, -7.153 + 402.773, -6.191, -7.021 + 404.865, -6.057, -6.887 + 406.967, -5.924, -6.752 + 409.080, -5.792, -6.616 + 411.204, -5.662, -6.478 + 413.340, -5.533, -6.340 + 415.486, -5.406, -6.201 + 417.644, -5.281, -6.062 + 419.812, -5.157, -5.921 + 421.992, -5.035, -5.780 + 424.183, -4.915, -5.638 + 426.386, -4.797, -5.495 + 428.600, -4.680, -5.352 + 430.826, -4.566, -5.209 + 433.063, -4.454, -5.065 + 435.311, -4.343, -4.921 + 437.572, -4.235, -4.776 + 439.844, -4.128, -4.631 + 442.128, -4.024, -4.486 + 444.424, -3.921, -4.341 + 446.732, -3.821, -4.195 + 449.051, -3.722, -4.049 + 451.383, -3.626, -3.904 + 453.727, -3.531, -3.758 + 456.083, -3.439, -3.612 + 458.451, -3.348, -3.466 + 460.832, -3.259, -3.321 + 463.225, -3.173, -3.176 + 465.630, -3.088, -3.031 + 468.048, -3.005, -2.886 + 470.478, -2.923, -2.742 + 472.921, -2.844, -2.599 + 475.377, -2.767, -2.456 + 477.845, -2.691, -2.314 + 480.327, -2.618, -2.174 + 482.821, -2.546, -2.034 + 485.328, -2.477, -1.896 + 487.848, -2.410, -1.759 + 490.381, -2.344, -1.623 + 492.928, -2.281, -1.489 + 495.487, -2.220, -1.358 + 498.060, -2.161, -1.228 + 500.646, -2.105, -1.101 + 503.246, -2.050, -0.976 + 505.859, -1.999, -0.854 + 508.486, -1.949, -0.734 + 511.126, -1.902, -0.618 + 513.780, -1.858, -0.505 + 516.448, -1.816, -0.395 + 519.130, -1.777, -0.288 + 521.826, -1.741, -0.186 + 524.535, -1.707, -0.087 + 527.259, -1.676, 0.008 + 529.997, -1.647, 0.099 + 532.749, -1.621, 0.185 + 535.515, -1.598, 0.267 + 538.296, -1.577, 0.345 + 541.091, -1.559, 0.418 + 543.901, -1.543, 0.486 + 546.725, -1.530, 0.550 + 549.564, -1.519, 0.608 + 552.418, -1.510, 0.662 + 555.286, -1.503, 0.711 + 558.170, -1.498, 0.755 + 561.068, -1.495, 0.795 + 563.982, -1.493, 0.829 + 566.910, -1.493, 0.858 + 569.854, -1.494, 0.882 + 572.813, -1.496, 0.901 + 575.787, -1.499, 0.915 + 578.777, -1.503, 0.924 + 581.783, -1.508, 0.927 + 584.804, -1.513, 0.926 + 587.840, -1.518, 0.920 + 590.893, -1.524, 0.908 + 593.961, -1.529, 0.891 + 597.045, -1.534, 0.869 + 600.145, -1.539, 0.842 + 603.262, -1.543, 0.809 + 606.394, -1.547, 0.771 + 609.543, -1.550, 0.727 + 612.708, -1.553, 0.679 + 615.890, -1.555, 0.624 + 619.088, -1.556, 0.565 + 622.303, -1.556, 0.500 + 625.534, -1.556, 0.429 + 628.782, -1.556, 0.353 + 632.047, -1.555, 0.272 + 635.329, -1.554, 0.185 + 638.628, -1.552, 0.093 + 641.944, -1.550, -0.004 + 645.278, -1.549, -0.106 + 648.628, -1.548, -0.212 + 651.996, -1.547, -0.324 + 655.382, -1.548, -0.439 + 658.785, -1.549, -0.559 + 662.206, -1.551, -0.682 + 665.645, -1.555, -0.809 + 669.101, -1.560, -0.938 + 672.575, -1.568, -1.071 + 676.068, -1.577, -1.205 + 679.578, -1.589, -1.342 + 683.107, -1.604, -1.480 + 686.654, -1.622, -1.619 + 690.220, -1.642, -1.759 + 693.804, -1.666, -1.900 + 697.407, -1.693, -2.041 + 701.028, -1.724, -2.181 + 704.668, -1.758, -2.321 + 708.327, -1.797, -2.460 + 712.005, -1.839, -2.599 + 715.703, -1.886, -2.736 + 719.419, -1.937, -2.872 + 723.155, -1.993, -3.006 + 726.910, -2.053, -3.139 + 730.684, -2.118, -3.270 + 734.478, -2.188, -3.399 + 738.292, -2.262, -3.526 + 742.126, -2.342, -3.651 + 745.980, -2.427, -3.772 + 749.853, -2.517, -3.890 + 753.747, -2.613, -4.004 + 757.661, -2.714, -4.113 + 761.595, -2.820, -4.217 + 765.550, -2.932, -4.313 + 769.525, -3.048, -4.402 + 773.521, -3.169, -4.482 + 777.537, -3.295, -4.551 + 781.575, -3.424, -4.609 + 785.633, -3.556, -4.654 + 789.713, -3.690, -4.685 + 793.813, -3.824, -4.702 + 797.935, -3.959, -4.704 + 802.079, -4.091, -4.690 + 806.244, -4.221, -4.662 + 810.430, -4.345, -4.619 + 814.639, -4.463, -4.563 + 818.869, -4.574, -4.495 + 823.121, -4.675, -4.417 + 827.395, -4.765, -4.331 + 831.691, -4.844, -4.239 + 836.010, -4.912, -4.143 + 840.351, -4.967, -4.044 + 844.715, -5.010, -3.945 + 849.101, -5.041, -3.846 + 853.510, -5.062, -3.751 + 857.942, -5.073, -3.659 + 862.397, -5.075, -3.571 + 866.875, -5.069, -3.488 + 871.376, -5.057, -3.409 + 875.901, -5.040, -3.336 + 880.449, -5.018, -3.266 + 885.021, -4.994, -3.200 + 889.617, -4.966, -3.135 + 894.236, -4.937, -3.071 + 898.880, -4.907, -3.006 + 903.547, -4.875, -2.938 + 908.239, -4.842, -2.866 + 912.955, -4.807, -2.787 + 917.696, -4.770, -2.699 + 922.461, -4.731, -2.602 + 927.251, -4.689, -2.494 + 932.066, -4.644, -2.374 + 936.906, -4.596, -2.243 + 941.771, -4.544, -2.100 + 946.661, -4.488, -1.947 + 951.577, -4.428, -1.785 + 956.518, -4.364, -1.615 + 961.485, -4.296, -1.440 + 966.477, -4.225, -1.262 + 971.496, -4.151, -1.083 + 976.541, -4.076, -0.905 + 981.611, -3.999, -0.731 + 986.709, -3.923, -0.563 + 991.832, -3.846, -0.402 + 996.982, -3.772, -0.249 + 1002.159, -3.699, -0.106 + 1007.363, -3.630, 0.026 + 1012.594, -3.564, 0.147 + 1017.852, -3.502, 0.257 + 1023.137, -3.444, 0.355 + 1028.450, -3.391, 0.442 + 1033.791, -3.343, 0.518 + 1039.159, -3.300, 0.582 + 1044.555, -3.260, 0.636 + 1049.979, -3.224, 0.680 + 1055.431, -3.192, 0.715 + 1060.911, -3.161, 0.740 + 1066.420, -3.130, 0.758 + 1071.958, -3.100, 0.768 + 1077.524, -3.067, 0.770 + 1083.119, -3.031, 0.766 + 1088.743, -2.990, 0.757 + 1094.397, -2.943, 0.742 + 1100.080, -2.888, 0.724 + 1105.792, -2.826, 0.702 + 1111.534, -2.755, 0.679 + 1117.306, -2.675, 0.656 + 1123.107, -2.587, 0.635 + 1128.939, -2.492, 0.617 + 1134.802, -2.391, 0.605 + 1140.694, -2.285, 0.601 + 1146.617, -2.176, 0.607 + 1152.571, -2.067, 0.624 + 1158.556, -1.958, 0.656 + 1164.572, -1.853, 0.702 + 1170.619, -1.753, 0.765 + 1176.698, -1.659, 0.844 + 1182.808, -1.572, 0.939 + 1188.950, -1.493, 1.048 + 1195.124, -1.424, 1.170 + 1201.330, -1.363, 1.303 + 1207.568, -1.310, 1.445 + 1213.838, -1.265, 1.591 + 1220.141, -1.226, 1.739 + 1226.477, -1.192, 1.885 + 1232.845, -1.162, 2.028 + 1239.247, -1.133, 2.163 + 1245.682, -1.103, 2.288 + 1252.151, -1.070, 2.401 + 1258.652, -1.033, 2.500 + 1265.188, -0.989, 2.584 + 1271.758, -0.938, 2.651 + 1278.362, -0.879, 2.701 + 1285.000, -0.812, 2.734 + 1291.672, -0.738, 2.750 + 1298.379, -0.657, 2.751 + 1305.121, -0.570, 2.737 + 1311.898, -0.480, 2.709 + 1318.711, -0.386, 2.669 + 1325.558, -0.291, 2.619 + 1332.441, -0.195, 2.561 + 1339.360, -0.097, 2.495 + 1346.315, 0.001, 2.423 + 1353.306, 0.102, 2.347 + 1360.333, 0.207, 2.268 + 1367.397, 0.317, 2.186 + 1374.497, 0.436, 2.104 + 1381.634, 0.566, 2.022 + 1388.809, 0.710, 1.942 + 1396.020, 0.869, 1.864 + 1403.269, 1.046, 1.791 + 1410.556, 1.241, 1.725 + 1417.881, 1.454, 1.668 + 1425.243, 1.684, 1.621 + 1432.644, 1.927, 1.587 + 1440.083, 2.182, 1.566 + 1447.561, 2.443, 1.560 + 1455.078, 2.706, 1.568 + 1462.633, 2.966, 1.588 + 1470.228, 3.219, 1.618 + 1477.862, 3.458, 1.655 + 1485.536, 3.682, 1.696 + 1493.250, 3.884, 1.737 + 1501.004, 4.064, 1.773 + 1508.798, 4.218, 1.802 + 1516.633, 4.345, 1.820 + 1524.508, 4.445, 1.825 + 1532.425, 4.519, 1.816 + 1540.382, 4.569, 1.793 + 1548.380, 4.598, 1.755 + 1556.421, 4.611, 1.706 + 1564.503, 4.611, 1.647 + 1572.626, 4.603, 1.582 + 1580.792, 4.593, 1.515 + 1589.001, 4.585, 1.449 + 1597.252, 4.580, 1.389 + 1605.546, 4.580, 1.336 + 1613.883, 4.585, 1.292 + 1622.263, 4.593, 1.256 + 1630.687, 4.600, 1.226 + 1639.155, 4.603, 1.199 + 1647.666, 4.597, 1.169 + 1656.222, 4.578, 1.131 + 1664.822, 4.542, 1.077 + 1673.467, 4.486, 1.003 + 1682.157, 4.410, 0.901 + 1690.891, 4.312, 0.770 + 1699.672, 4.194, 0.605 + 1708.497, 4.057, 0.408 + 1717.369, 3.904, 0.183 + 1726.287, 3.737, -0.062 + 1735.251, 3.559, -0.316 + 1744.261, 3.371, -0.561 + 1753.318, 3.175, -0.774 + 1762.423, 2.973, -0.933 + 1771.574, 2.765, -1.015 + 1780.773, 2.554, -1.007 + 1790.020, 2.341, -0.906 + 1799.315, 2.130, -0.722 + 1808.658, 1.925, -0.474 + 1818.050, 1.730, -0.186 + 1827.491, 1.548, 0.120 + 1836.980, 1.383, 0.423 + 1846.519, 1.233, 0.711 + 1856.107, 1.096, 0.976 + 1865.745, 0.962, 1.212 + 1875.433, 0.822, 1.420 + 1885.172, 0.662, 1.601 + 1894.961, 0.468, 1.757 + 1904.801, 0.228, 1.889 + 1914.692, -0.067, 1.999 + 1924.634, -0.418, 2.085 + 1934.628, -0.816, 2.147 + 1944.674, -1.236, 2.180 + 1954.772, -1.636, 2.182 + 1964.922, -1.951, 2.148 + 1975.125, -2.109, 2.078 + 1985.381, -2.054, 1.972 + 1995.691, -1.772, 1.834 + 2006.053, -1.300, 1.675 + 2016.470, -0.706, 1.511 + 2026.941, -0.065, 1.362 + 2037.466, 0.565, 1.252 + 2048.046, 1.144, 1.200 + 2058.681, 1.649, 1.220 + 2069.371, 2.069, 1.310 + 2080.116, 2.402, 1.461 + 2090.917, 2.651, 1.655 + 2101.775, 2.827, 1.875 + 2112.688, 2.942, 2.110 + 2123.659, 3.013, 2.356 + 2134.686, 3.057, 2.617 + 2145.771, 3.088, 2.903 + 2156.913, 3.121, 3.224 + 2168.113, 3.163, 3.585 + 2179.371, 3.221, 3.983 + 2190.688, 3.293, 4.409 + 2202.063, 3.376, 4.845 + 2213.498, 3.467, 5.275 + 2224.992, 3.560, 5.683 + 2236.545, 3.651, 6.058 + 2248.159, 3.739, 6.392 + 2259.833, 3.823, 6.681 + 2271.567, 3.904, 6.924 + 2283.363, 3.983, 7.120 + 2295.219, 4.061, 7.269 + 2307.138, 4.140, 7.373 + 2319.118, 4.222, 7.432 + 2331.160, 4.309, 7.448 + 2343.265, 4.401, 7.420 + 2355.433, 4.501, 7.351 + 2367.663, 4.609, 7.241 + 2379.958, 4.726, 7.095 + 2392.316, 4.854, 6.919 + 2404.739, 4.994, 6.727 + 2417.225, 5.150, 6.537 + 2429.777, 5.322, 6.372 + 2442.394, 5.514, 6.255 + 2455.077, 5.725, 6.205 + 2467.825, 5.950, 6.231 + 2480.639, 6.182, 6.326 + 2493.520, 6.411, 6.475 + 2506.468, 6.624, 6.656 + 2519.484, 6.811, 6.850 + 2532.566, 6.961, 7.043 + 2545.717, 7.071, 7.227 + 2558.936, 7.140, 7.399 + 2572.224, 7.171, 7.557 + 2585.580, 7.168, 7.696 + 2599.006, 7.140, 7.810 + 2612.502, 7.093, 7.891 + 2626.068, 7.036, 7.930 + 2639.704, 6.970, 7.920 + 2653.411, 6.897, 7.856 + 2667.189, 6.809, 7.741 + 2681.039, 6.695, 7.578 + 2694.960, 6.543, 7.374 + 2708.954, 6.342, 7.137 + 2723.021, 6.083, 6.875 + 2737.160, 5.768, 6.592 + 2751.374, 5.405, 6.291 + 2765.660, 5.012, 5.976 + 2780.021, 4.611, 5.644 + 2794.457, 4.228, 5.297 + 2808.968, 3.882, 4.934 + 2823.554, 3.579, 4.561 + 2838.215, 3.316, 4.189 + 2852.953, 3.080, 3.837 + 2867.767, 2.858, 3.527 + 2882.659, 2.639, 3.279 + 2897.627, 2.422, 3.105 + 2912.674, 2.209, 3.001 + 2927.798, 2.008, 2.948 + 2943.001, 1.829, 2.925 + 2958.283, 1.683, 2.904 + 2973.644, 1.577, 2.866 + 2989.085, 1.515, 2.798 + 3004.606, 1.486, 2.694 + 3020.208, 1.469, 2.558 + 3035.891, 1.435, 2.404 + 3051.655, 1.355, 2.251 + 3067.501, 1.208, 2.118 + 3083.430, 0.990, 2.019 + 3099.441, 0.726, 1.951 + 3115.535, 0.467, 1.889 + 3131.713, 0.280, 1.797 + 3147.975, 0.218, 1.632 + 3164.321, 0.288, 1.355 + 3180.752, 0.457, 0.938 + 3197.269, 0.677, 0.372 + 3213.871, 0.918, -0.336 + 3230.559, 1.170, -1.138 + 3247.335, 1.432, -1.928 + 3264.197, 1.698, -2.527 + 3281.147, 1.947, -2.742 + 3298.184, 2.146, -2.510 + 3315.311, 2.264, -1.959 + 3332.526, 2.285, -1.292 + 3349.830, 2.216, -0.668 + 3367.225, 2.081, -0.171 + 3384.710, 1.917, 0.171 + 3402.285, 1.757, 0.372 + 3419.952, 1.612, 0.464 + 3437.710, 1.465, 0.495 + 3455.561, 1.285, 0.502 + 3473.505, 1.051, 0.513 + 3491.541, 0.764, 0.543 + 3509.672, 0.452, 0.604 + 3527.896, 0.148, 0.703 + 3546.215, -0.134, 0.833 + 3564.629, -0.419, 0.961 + 3583.139, -0.749, 1.033 + 3601.745, -1.158, 0.988 + 3620.448, -1.629, 0.786 + 3639.247, -2.095, 0.420 + 3658.144, -2.470, -0.075 + 3677.140, -2.699, -0.628 + 3696.234, -2.803, -1.164 + 3715.427, -2.843, -1.626 + 3734.720, -2.847, -1.983 + 3754.113, -2.776, -2.195 + 3773.607, -2.564, -2.204 + 3793.202, -2.200, -1.968 + 3812.898, -1.779, -1.529 + 3832.697, -1.441, -1.010 + 3852.599, -1.297, -0.548 + 3872.604, -1.386, -0.256 + 3892.713, -1.676, -0.200 + 3912.927, -2.088, -0.391 + 3933.245, -2.529, -0.764 + 3953.669, -2.942, -1.180 + 3974.199, -3.338, -1.481 + 3994.835, -3.787, -1.589 + 4015.579, -4.352, -1.567 + 4036.430, -5.023, -1.565 + 4057.390, -5.679, -1.724 + 4078.459, -6.101, -2.132 + 4099.637, -6.069, -2.834 + 4120.925, -5.561, -3.859 + 4142.323, -4.789, -5.264 + 4163.833, -4.018, -7.179 + 4185.454, -3.441, -9.843 + 4207.187, -3.175, -13.476 + 4229.034, -3.291, -16.662 + 4250.993, -3.819, -15.263 + 4273.067, -4.728, -12.543 + 4295.256, -5.939, -10.774 + 4317.559, -7.337, -9.828 + 4339.979, -8.753, -9.406 + 4362.515, -9.868, -9.261 + 4385.168, -10.286, -9.180 + 4407.938, -10.048, -8.976 + 4430.827, -9.665, -8.512 + 4453.835, -9.483, -7.790 + 4476.962, -9.421, -6.959 + 4500.209, -9.119, -6.188 + 4523.577, -8.363, -5.571 + 4547.066, -7.338, -5.118 + 4570.678, -6.360, -4.781 + 4594.411, -5.614, -4.477 + 4618.268, -5.155, -4.149 + 4642.249, -4.983, -3.808 + 4666.355, -5.087, -3.525 + 4690.586, -5.407, -3.363 + 4714.942, -5.739, -3.307 + 4739.425, -5.714, -3.227 + 4764.035, -5.084, -2.947 + 4788.773, -4.053, -2.415 + 4813.639, -3.021, -1.776 + 4838.635, -2.223, -1.244 + 4863.760, -1.716, -0.947 + 4889.016, -1.472, -0.887 + 4914.403, -1.419, -0.968 + 4939.921, -1.466, -1.066 + 4965.572, -1.541, -1.123 + 4991.357, -1.614, -1.183 + 5017.275, -1.708, -1.327 + 5043.328, -1.870, -1.594 + 5069.516, -2.127, -1.955 + 5095.840, -2.441, -2.362 + 5122.301, -2.678, -2.771 + 5148.899, -2.625, -3.098 + 5175.636, -2.168, -3.156 + 5202.511, -1.448, -2.786 + 5229.525, -0.746, -2.056 + 5256.680, -0.276, -1.223 + 5283.976, -0.114, -0.509 + 5311.414, -0.222, -0.004 + 5338.994, -0.511, 0.313 + 5366.718, -0.897, 0.503 + 5394.585, -1.306, 0.622 + 5422.597, -1.659, 0.675 + 5450.755, -1.905, 0.629 + 5479.059, -2.070, 0.477 + 5507.509, -2.242, 0.247 + 5536.108, -2.518, -0.078 + 5564.855, -2.954, -0.599 + 5593.751, -3.515, -1.372 + 5622.797, -4.057, -2.256 + 5651.994, -4.428, -2.984 + 5681.343, -4.666, -3.456 + 5710.844, -4.998, -3.835 + 5740.499, -5.593, -4.217 + 5770.307, -6.404, -4.443 + 5800.270, -7.202, -4.380 + 5830.389, -7.699, -4.204 + 5860.664, -7.679, -4.240 + 5891.096, -7.167, -4.652 + 5921.686, -6.465, -5.360 + 5952.435, -6.012, -6.068 + 5983.344, -6.175, -6.427 + 6014.413, -7.093, -6.494 + 6045.644, -8.490, -6.699 + 6077.037, -9.407, -7.254 + 6108.593, -9.036, -7.923 + 6140.312, -8.078, -8.234 + 6172.197, -7.434, -8.040 + 6204.247, -7.359, -7.681 + 6236.463, -7.728, -7.472 + 6268.847, -8.259, -7.460 + 6301.399, -8.740, -7.533 + 6334.119, -9.255, -7.542 + 6367.010, -10.077, -7.421 + 6400.072, -11.221, -7.290 + 6433.305, -12.171, -7.449 + 6466.711, -12.438, -8.167 + 6500.290, -12.441, -9.431 + 6534.043, -12.716, -10.924 + 6567.972, -13.375, -12.318 + 6602.077, -14.522, -13.594 + 6636.360, -16.291, -14.726 + 6670.820, -18.101, -15.238 + 6705.459, -18.039, -15.202 + 6740.278, -16.402, -15.660 + 6775.278, -15.051, -17.365 + 6810.459, -14.271, -20.048 + 6845.823, -13.987, -21.818 + 6881.371, -14.444, -21.562 + 6917.104, -16.046, -20.989 + 6953.022, -18.976, -20.814 + 6989.126, -21.513, -20.848 + 7025.418, -19.935, -20.559 + 7061.898, -17.556, -19.332 + 7098.568, -16.478, -17.667 + 7135.428, -16.342, -16.496 + 7172.480, -16.694, -16.255 + 7209.724, -17.631, -16.975 + 7247.162, -19.119, -18.228 + 7284.793, -20.456, -19.398 + 7322.621, -20.687, -20.406 + 7360.644, -19.696, -21.304 + 7398.865, -18.155, -20.938 + 7437.285, -16.363, -18.936 + 7475.904, -15.019, -16.690 + 7514.724, -14.858, -15.023 + 7553.745, -15.810, -14.036 + 7592.969, -17.228, -13.553 + 7632.396, -17.894, -13.531 + 7672.028, -17.607, -13.920 + 7711.866, -17.483, -14.575 + 7751.911, -17.746, -15.310 + 7792.164, -17.784, -15.947 + 7832.626, -17.193, -16.403 + 7873.298, -16.241, -16.281 + 7914.181, -15.411, -15.424 + 7955.276, -15.321, -14.933 + 7996.585, -16.266, -15.499 + 8038.109, -17.631, -16.236 + 8079.847, -18.283, -16.140 + 8121.803, -18.228, -15.764 + 8163.977, -18.534, -15.990 + 8206.369, -19.440, -16.806 + 8248.982, -20.279, -17.370 + 8291.816, -21.029, -17.339 + 8334.872, -21.487, -16.961 + 8378.152, -20.743, -15.922 + 8421.657, -20.368, -15.269 + 8465.387, -22.097, -16.216 + 8509.345, -24.351, -17.655 + 8553.531, -25.517, -18.692 + 8597.946, -27.087, -20.607 + 8642.592, -27.679, -22.159 + 8687.470, -27.150, -21.397 + 8732.581, -28.437, -20.818 + 8777.926, -31.018, -21.232 + 8823.506, -33.644, -22.472 + 8869.323, -34.609, -24.474 + 8915.379, -33.982, -25.161 + 8961.673, -33.633, -25.372 + 9008.208, -33.089, -26.322 + 9054.984, -35.767, -28.407 + 9102.003, -41.735, -32.372 + 9149.266, -34.312, -33.912 + 9196.775, -32.116, -31.573 + 9244.531, -32.322, -29.695 + 9292.534, -35.271, -27.913 + 9340.787, -40.656, -27.612 + 9389.290, -43.153, -29.349 + 9438.045, -39.375, -30.869 + 9487.053, -32.993, -29.169 + 9536.316, -28.298, -30.903 + 9585.835, -25.244, -35.381 + 9635.610, -25.701, -35.362 + 9685.645, -26.142, -32.926 + 9735.939, -25.770, -30.588 + 9786.494, -27.603, -29.455 + 9837.311, -28.201, -30.668 + 9888.393, -26.653, -32.910 + 9939.740, -25.131, -34.270 + 9991.353, -24.468, -35.300 + 10043.235, -23.680, -30.777 + 10095.385, -24.079, -28.870 + 10147.807, -24.705, -27.825 + 10200.501, -21.853, -24.193 + 10253.468, -19.870, -20.828 + 10306.711, -20.054, -19.489 + 10360.230, -18.561, -19.124 + 10414.026, -16.977, -19.539 + 10468.103, -18.750, -22.053 + 10522.460, -19.384, -22.842 + 10577.099, -16.125, -20.135 + 10632.022, -14.385, -19.305 + 10687.230, -13.973, -19.471 + 10742.725, -13.647, -18.969 + 10798.508, -14.294, -19.198 + 10854.581, -14.520, -19.317 + 10910.944, -13.225, -18.710 + 10967.601, -12.438, -18.775 + 11024.552, -12.464, -19.154 + 11081.798, -12.716, -18.998 + 11139.342, -13.167, -18.335 + 11197.184, -13.560, -17.458 + 11255.327, -11.920, -15.783 + 11313.772, -10.510, -14.429 + 11372.520, -10.493, -13.845 + 11431.573, -10.929, -13.800 + 11490.933, -10.800, -13.789 + 11550.601, -11.014, -14.439 + 11610.579, -11.331, -14.387 + 11670.869, -11.260, -14.252 + 11731.471, -10.606, -13.932 + 11792.389, -10.766, -13.623 + 11853.622, -12.072, -13.561 + 11915.174, -13.699, -14.575 + 11977.045, -13.911, -14.760 + 12039.237, -13.612, -13.833 + 12101.752, -14.254, -15.248 + 12164.592, -14.255, -16.082 + 12227.759, -15.246, -15.230 + 12291.253, -17.082, -15.578 + 12355.077, -17.119, -16.278 + 12419.232, -18.754, -17.374 + 12483.721, -20.074, -18.117 + 12548.544, -19.721, -17.465 + 12613.704, -22.686, -17.832 + 12679.202, -21.220, -19.830 + 12745.041, -19.104, -21.238 + 12811.221, -19.327, -23.423 + 12877.745, -18.651, -29.805 + 12944.614, -17.624, -25.798 + 13011.831, -18.196, -21.551 + 13079.397, -18.380, -19.626 + 13147.313, -17.632, -19.430 + 13215.582, -17.013, -20.859 + 13284.206, -15.295, -20.423 + 13353.186, -14.005, -18.827 + 13422.524, -13.332, -18.428 + 13492.222, -14.533, -18.642 + 13562.282, -14.689, -17.755 + 13632.706, -13.633, -17.931 + 13703.496, -13.694, -17.529 + 13774.653, -14.440, -17.448 + 13846.180, -15.071, -18.632 + 13918.078, -15.652, -19.169 + 13990.349, -15.433, -18.595 + 14062.996, -15.518, -17.754 + 14136.020, -15.975, -16.881 + 14209.423, -15.197, -15.931 + 14283.207, -15.387, -16.473 + 14357.375, -16.436, -17.261 + 14431.927, -17.499, -17.709 + 14506.867, -17.735, -17.362 + 14582.196, -18.070, -17.592 + 14657.916, -17.755, -17.638 + 14734.029, -18.716, -17.712 + 14810.537, -18.667, -18.692 + 14887.443, -18.899, -21.009 + 14964.748, -19.456, -21.171 + 15042.454, -20.952, -21.413 + 15120.564, -21.670, -21.334 + 15199.080, -21.980, -22.066 + 15278.003, -25.323, -23.947 + 15357.336, -26.823, -24.928 + 15437.081, -25.991, -27.074 + 15517.240, -27.629, -30.389 + 15597.815, -30.773, -32.039 + 15678.809, -35.757, -31.495 + 15760.223, -29.072, -31.792 + 15842.060, -28.077, -35.229 + 15924.322, -27.585, -42.646 + 16007.012, -25.155, -34.823 + 16090.130, -25.171, -32.693 + 16173.680, -25.795, -32.305 + 16257.664, -25.643, -31.496 + 16342.084, -25.355, -32.089 + 16426.943, -25.736, -31.500 + 16512.242, -26.246, -30.598 + 16597.984, -25.192, -29.239 + 16684.171, -27.304, -30.238 + 16770.806, -28.107, -31.071 + 16857.890, -27.198, -30.781 + 16945.427, -27.967, -33.128 + 17033.418, -29.094, -33.147 + 17121.867, -30.431, -33.102 + 17210.774, -32.619, -34.209 + 17300.143, -34.268, -35.872 + 17389.977, -34.497, -36.592 + 17480.276, -31.588, -34.715 + 17571.045, -37.675, -40.715 + 17662.285, -37.569, -39.255 + 17753.999, -42.735, -42.000 + 17846.189, -49.213, -49.727 + 17938.857, -47.611, -50.239 + 18032.007, -45.206, -42.393 + 18125.641, -51.820, -50.161 + 18219.761, -55.094, -53.106 + 18314.369, -52.647, -50.265 + 18409.469, -53.595, -49.285 + 18505.062, -49.637, -61.368 + 18601.152, -52.866, -55.880 + 18697.741, -44.759, -50.507 + 18794.832, -43.593, -55.002 + 18892.426, -42.729, -53.713 + 18990.528, -42.671, -50.713 + 19089.139, -40.584, -49.937 + 19188.261, -34.787, -43.509 + 19287.899, -41.757, -48.445 + 19388.054, -32.058, -42.623 + 19488.729, -31.664, -39.266 + 19589.927, -31.893, -36.969 + 19691.650, -30.779, -35.801 + 19793.901, -31.022, -36.402 + 19896.684, -32.701, -36.436 + 20000.000, -36.437, -39.544 diff --git a/src/audio/eq_iir/tune/sof_ucm2_eq_example.xlsx b/src/audio/eq_iir/tune/sof_ucm2_eq_example.xlsx new file mode 100644 index 000000000000..6e8ece702ff7 Binary files /dev/null and b/src/audio/eq_iir/tune/sof_ucm2_eq_example.xlsx differ diff --git a/src/audio/eq_iir/tune/sof_ucm2_eq_generate.m b/src/audio/eq_iir/tune/sof_ucm2_eq_generate.m new file mode 100644 index 000000000000..2eb2fc34f291 --- /dev/null +++ b/src/audio/eq_iir/tune/sof_ucm2_eq_generate.m @@ -0,0 +1,565 @@ +function sof_ucm2_eq_generate(sys_vendor, product_name, endpoint, meas_file) + +% SOF_UCM2_EQ_GENERATE Fit IIR + FIR endpoint EQ from a measurement. +% +% sof_ucm2_eq_generate(SYS_VENDOR, PRODUCT_NAME, ENDPOINT, MEAS_FILE) +% +% SYS_VENDOR DMI /sys/devices/virtual/dmi/id/sys_vendor value, e.g. +% 'Acme Ltd.'. Kept verbatim (with spaces and case) for the +% product_configs directory name. +% PRODUCT_NAME DMI product_name value, e.g. 'Model 100'. Kept +% verbatim for the .conf file name. +% ENDPOINT Endpoint being tuned, e.g. 'speaker' or 'headphone'. Used +% lower case in blob file names and capitalized in the +% Define.PostMixerPlayback... UCM keys. +% MEAS_FILE Path to a comma separated numbers text file whose first +% column is the measurement frequency in Hz and whose +% remaining columns are one or more measured magnitude +% traces in dB. Multiple traces are averaged. +% Such a file can be produced with sof_mls_freq_resp.m. +% Excel workbooks (.xls, .xlsx, .xlsm) and OpenDocument +% spreadsheets (.ods) are also accepted; the format is +% detected from the file extension and imported via +% xlsread from the Octave 'io' package. The workbook +% is expected to contain the same [freq, resp...] numeric +% layout with no header row, matching +% sof_ucm2_eq_example.xlsx. +% +% A cell array of paths is also accepted, e.g. +% { 'mls-a.txt', 'mls-b.txt', 'mls-c.txt' }. Each file is +% loaded independently (per-file column averaging still +% applies) and the per-file magnitudes are then averaged +% together. All files must share the same frequency +% grid; a mismatch raises an error. +% +% The binary IIR and FIR blobs consumed by UCM are written to +% ./ucm2_blobs_sof/ipc4/eq_iir/___iir.bin +% ./ucm2_blobs_sof/ipc4/eq_fir/___fir.bin +% +% The matching sof-ctl text dumps for manual testing are written under +% the SOF source tree (independent of the caller's current directory): +% /tools/ctl/ipc4/eq_iir/___iir.txt +% /tools/ctl/ipc4/eq_fir/___fir.txt +% +% and a UCM include file that will be picked up automatically by UCM is +% written to +% ./ucm2_blobs_sof/product_configs//.conf +% +% Run this to see a design example and produced configuration for ALSA UCMv2 +% sof_ucm2_eq_generate('example', 'example', 'speaker', 'sof_ucm2_eq_example.txt'); +% or +% sof_ucm2_eq_generate('example', 'example', 'speaker', 'sof_ucm2_eq_example.xlsx'); + +% SPDX-License-Identifier: BSD-3-Clause +% +% Copyright (c) 2026, Intel Corporation. + +if nargin < 4 + help sof_ucm2_eq_generate + printf('\n'); + error('Usage: sof_ucm2_eq_generate(sys_vendor, product_name, endpoint, meas_file)'); +end + +%% Validate DMI and endpoint inputs before they are used to build any +%% path or UCM key. sys_vendor and product_name are kept verbatim as +%% directory / file name components under product_configs/, so they must +%% not contain path separators or traversal segments. endpoint is +%% capitalized and dropped into Define.PostMixerPlayback... +%% keys, so it has to be a plain letter identifier. +validate_dmi_field('sys_vendor', sys_vendor); +validate_dmi_field('product_name', product_name); +validate_endpoint(endpoint); + +%% Load the signal package up front so the script fails cleanly on a +%% system missing it, before any output directories are created. The io +%% package is only pulled in when the measurement file is a spreadsheet +%% (see load_measurement below), so text-only users do not need it. +pkg load signal; + +%% Derive blob base name and output paths from the DMI info + endpoint. +%% The binary blobs go into a UCM-shaped staging tree (ucm2_blobs_sof/) +%% that mirrors the alsa-ucm-conf layout. The sof-ctl text dumps go into +%% the regular tools/ctl/ipc4/eq_{iir,fir}/ locations so they can be used +%% for manual testing with sof-ctl the same way as the other tune scripts; +%% UCM itself does not consume these .txt files. The tools/ctl path is +%% derived from the location of this script so it works regardless of the +%% caller's current directory. +endpoint_lc = lower(endpoint); +base = sprintf('%s_%s_%s', endpoint_lc, ... + sanitize_name('sys_vendor', sys_vendor), ... + sanitize_name('product_name', product_name)); +out_root = 'ucm2_blobs_sof'; +cpath4 = fullfile(out_root, 'ipc4'); +iir_bin = fullfile('eq_iir', [base '_iir.bin']); +fir_bin = fullfile('eq_fir', [base '_fir.bin']); +conf_dir = fullfile(out_root, 'product_configs', sys_vendor); +conf_file = fullfile(conf_dir, [product_name '.conf']); +ensure_dir(fullfile(cpath4, 'eq_iir')); +ensure_dir(fullfile(cpath4, 'eq_fir')); +ensure_dir(conf_dir); + +script_dir = fileparts(mfilename('fullpath')); +ctl_root = fullfile(script_dir, '..', '..', '..', '..', 'tools', 'ctl', 'ipc4'); +ctl_iir_dir = fullfile(ctl_root, 'eq_iir'); +ctl_fir_dir = fullfile(ctl_root, 'eq_fir'); +ensure_dir(ctl_iir_dir); +ensure_dir(ctl_fir_dir); +iir_txt = fullfile(ctl_iir_dir, [base '_iir.txt']); +fir_txt = fullfile(ctl_fir_dir, [base '_fir.txt']); + +enable_common_paths(script_dir, true); + +%% Base equalizer setup +eq = sof_eq_defaults(); +eq.fs = 48e3; +eq.logsmooth_plot = 1.0; +eq.logsmooth_eq = 1.0; +eq.enable_iir = 1; +eq.enable_fir = 1; +eq.iir_norm_type = 'loudness'; +eq.iir_norm_offs_db = -1; +eq.fir_norm_type = 'loudness'; +eq.fir_norm_offs_db = -1; +eq.p_fmin = 20; +eq.p_fmax = 20e3; + +%% Parametric target: keep the corrected response band-limited so the fit +%% does not chase noise below 80 Hz (below the speaker's useful range) or +%% brighten the top octave. HP2 80 Hz rolls off sub-bass in the target, +%% LP2 10 kHz tames the highs. Both the IIR stage fit (stage_rms) and the +%% FIR design in sof_eq_compute honor eq.t_db that this generates. +eq.parametric_target_response = [ ... + eq.PEQ_HP2 80 0 0; ... + eq.PEQ_LP2 10000 0 0; ... +]; + +%% Load measurement, fit IIR + FIR, compute the final response and export +eq = load_measurement(eq, meas_file); +eq = design_iir_stages(eq); +eq = configure_fir(eq); +eq = sof_eq_compute(eq); +sof_eq_plot(eq, 1); +export_blobs(eq, cpath4, iir_txt, iir_bin, fir_txt, fir_bin); +write_product_conf(conf_file, sys_vendor, product_name, endpoint, base, eq); + +enable_common_paths(script_dir, false); + +printf('\n'); +printf('To install these blobs into alsa-ucm-conf, copy the contents of\n'); +printf('%s/ recursively into alsa-ucm-conf/ucm2/blobs/sof:\n', out_root); +printf(' cp -r %s/* /ucm2/blobs/sof/\n', out_root); +printf('sof-ctl text dumps written under tools/ctl/ipc4/eq_{iir,fir}/.\n'); + +end + +%% ----------------------------------------------------------------------- +%% Measurement loading +%% ----------------------------------------------------------------------- +function eq = load_measurement(eq, meas_file) +%% meas_file is either a single path or a cell array of paths. Each file +%% is loaded and its response columns (if more than one) are averaged +%% into a single trace, then the per-file traces are averaged together. +%% All files must share the exact same frequency grid; any mismatch +%% aborts before any fit is attempted. +if iscell(meas_file) + files = meas_file; +elseif ischar(meas_file) + files = { meas_file }; +else + error(['meas_file must be a char path or a cell array of ' ... + 'char paths (got %s)'], class(meas_file)); +end + +if isempty(files) + error('meas_file must not be an empty cell array'); +end +for k = 1:numel(files) + if ~ischar(files{k}) || isempty(files{k}) + error(['meas_file{%d} must be a non-empty char path ' ... + '(got %s)'], k, class(files{k})); + end +end + +f_ref = []; +m_stack = []; +for k = 1:numel(files) + fn = files{k}; + if ~exist(fn, 'file') + error('Measurement file not found: %s', fn); + end + [~, ~, ext] = fileparts(fn); + switch lower(ext) + case {'.xls', '.xlsx', '.xlsm', '.ods'} + pkg load io; + meas = xlsread(fn); + otherwise + %% dlmread with no explicit delimiter auto-detects whitespace or + %% comma separation, which covers both the sof_mls_freq_resp.m + %% output and legacy whitespace-delimited measurement files. + meas = dlmread(fn); + end + if size(meas, 2) < 2 + error('%s must have at least a frequency column and one response column', ... + fn); + end + f_k = meas(:, 1); + if size(meas, 2) == 2 + m_k = meas(:, 2); + else + m_k = mean(meas(:, 2:end), 2); + fprintf('Averaged %d response columns from %s\n', ... + size(meas, 2) - 1, fn); + end + if isempty(f_ref) + f_ref = f_k; + m_stack = m_k; + else + if ~isequal(size(f_k), size(f_ref)) || any(f_k(:) ~= f_ref(:)) + error(['Frequency grid of %s does not match %s. ' ... + 'All measurement files must share the same ' ... + '[freq, resp...] grid.'], fn, files{1}); + end + m_stack = [m_stack, m_k]; + end +end + +eq.raw_f = f_ref; +if size(m_stack, 2) == 1 + eq.raw_m_db = m_stack; +else + eq.raw_m_db = mean(m_stack, 2); + fprintf('Averaged %d measurement files\n', size(m_stack, 2)); +end +end + +%% ----------------------------------------------------------------------- +%% Multi-stage IIR fit +%% ----------------------------------------------------------------------- +function eq = design_iir_stages(eq) + +%% Cap on the combined response shaping between the mid PN2 (stage 1) and +% the bass LS2 (stage 2). If stage 1 pulls the mids down by A dB, the +% LS2 upper bound is reduced so that bass_boost + |mid_atten| does not +% exceed this limit. This keeps the bass-to-mid tilt from becoming +% excessive (e.g. 26 dB total is already an aggressive shape). +max_bass_plus_mid_atten_db = 26; + +%% Filter budget (4 biquads max): +% 1) HP2 at 80 Hz to protect the speaker +% 2) PN2 mid shaper -> fc [2000, 4000], gain [-12, +6], Q [0.5, 1.0] +% 3) LS2 bass boost -> fc [120, 1000], gain [0, +12] +% 4) PN2 fine correction (low band) -> fc [200, 4000], gain [-6, +12], Q [0.5, 1.0] +% Fitting order matters: the mid PN2 is fit first so the subsequent low +% shelf can settle on top of an already-flattened midrange instead of +% chasing a mid bump with bass gain. The remaining high-band correction +% is left to the mid-band FIR that runs after the IIR, which keeps the +% IIR order low and avoids the audible ringing seen with a larger IIR +% budget. +hp_fc = 80; + +opts = optimset('Display', 'notify', 'MaxIter', 300, 'MaxFunEvals', 2000, ... + 'TolX', 1e-3, 'TolFun', 1e-3); + +peq_fixed = [eq.PEQ_HP2, hp_fc, 0, 0]; + +%% Stage 1: fit a mid PN2 peak/notch first, on top of the HP2. Doing this +% before the low shelf keeps the shelf from over-compensating for a +% broad midrange bump. The gain upper bound is kept low (+6 dB) so this +% stage stays a mid *attenuator* rather than a boost. +% Params: [fc, gain, Q]. fc [2000, 4000] Hz, gain [-12, +6] dB, Q [0.5, 1.0]. +fmin_fit = 400; +fmax_fit = 7000; +p1_0 = [2500, -6, 0.6]; +p1_bounds = [2000, 4000; -12, +6; 0.5, 1.0]; +p1 = fminsearch(@(p) stage_rms(peq_fixed, pn_row(p, p1_bounds, eq), eq, fmin_fit, fmax_fit), ... + p1_0, opts); +peq_fixed = [peq_fixed; pn_row(p1, p1_bounds, eq)]; +fprintf('Stage 1 (mid PN2): fc=%.1f Hz g=%.2f dB Q=%.2f\n', ... + clamp(p1(1), p1_bounds(1,1), p1_bounds(1,2)), ... + clamp(p1(2), p1_bounds(2,1), p1_bounds(2,2)), ... + clamp(p1(3), p1_bounds(3,1), p1_bounds(3,2))); + +%% Stage 2: fit the low shelf on top of the flattened midrange. +% Params: [ls_fc, ls_g]. fc [120, 1000] Hz, gain [0, +12] dB. +% The upper bass gain bound is shrunk when stage 1 attenuated the mids, +% so the total bass-to-mid shaping stays within max_bass_plus_mid_atten_db. +fmin_fit = 200; +fmax_fit = 2000; +ls0 = [200, 8]; +ls_bounds = [120, 1000; 0, 12]; +mid_atten_db = min(0, clamp(p1(2), p1_bounds(2,1), p1_bounds(2,2))); +ls_bounds(2,2) = min(ls_bounds(2,2), max(0, max_bass_plus_mid_atten_db - abs(mid_atten_db))); +if ls_bounds(2,2) < ls0(2) + ls0(2) = ls_bounds(2,2); +end +fprintf('Stage 2 bass gain upper bound = %.2f dB (mid atten %.2f dB, cap %.1f dB)\n', ... + ls_bounds(2,2), mid_atten_db, max_bass_plus_mid_atten_db); +ls = fminsearch(@(p) stage_rms(peq_fixed, shelf_row(eq.PEQ_LS2, p, ls_bounds), ... + eq, fmin_fit, fmax_fit), ls0, opts); +peq_fixed = [peq_fixed; shelf_row(eq.PEQ_LS2, ls, ls_bounds)]; +fprintf('Stage 2 (LS2): fc=%.1f Hz g=%.2f dB\n', clamp(ls(1), ls_bounds(1,1), ls_bounds(1,2)), ... + clamp(ls(2), ls_bounds(2,1), ls_bounds(2,2))); + +%% Widen the fit band for the fine correction stage: below 400 Hz the LS2 +% already dominates and above the mid we still want to shape the response +% out to 7 kHz. +fmin_fit = 400; +fmax_fit = 7000; + +%% Stage 3: single fine correction across the low/mid band. The higher +% frequencies are left to the mid-band FIR, so this biquad can focus on +% whatever residual the shelf + mid PN2 left behind. +% Params: [fc, gain, Q]. fc [200, 4000] Hz, gain [-6, +12] dB, Q [0.5, 1.0]. +p2_0 = [800, -3, 1.0]; +p2_bounds = [200, 4000; -6, 12; 0.5, 1.0]; +p2 = fminsearch(@(p) stage_rms(peq_fixed, pn_row(p, p2_bounds, eq), eq, fmin_fit, fmax_fit), ... + p2_0, opts); +peq_fixed = [peq_fixed; pn_row(p2, p2_bounds, eq)]; +fprintf('Stage 3 (fine PN2): fc=%.1f Hz g=%.2f dB Q=%.2f\n', ... + clamp(p2(1), p2_bounds(1,1), p2_bounds(1,2)), ... + clamp(p2(2), p2_bounds(2,1), p2_bounds(2,2)), ... + clamp(p2(3), p2_bounds(3,1), p2_bounds(3,2))); + +eq.peq = peq_fixed; +end + +%% ----------------------------------------------------------------------- +%% FIR configuration for mid-band residual correction +%% ----------------------------------------------------------------------- +function eq = configure_fir(eq) +%% The IIR takes care of the coarse bass shelf plus two low/mid PN2 +% shapers. Whatever residual vs. the target is left after the IIR +% (fir_compensate_iir = 1 in the defaults) is picked up here by a short +% minimum-phase FIR limited to [fmin_fir, fmax_fir], so it does not +% spend taps on the LF/HF regions the IIR already handles or where the +% measurement is unreliable. +eq.fir_length = 63; +eq.fir_beta = 10; +eq.fir_minph = 1; +eq.fir_autoband = 0; +eq.fmin_fir = 400; +eq.fmax_fir = 7000; +fprintf('FIR: length=%d taps, mid band [%d, %d] Hz\n', ... + eq.fir_length, eq.fmin_fir, eq.fmax_fir); +end + +%% ----------------------------------------------------------------------- +%% IIR + FIR blob packing and export +%% ----------------------------------------------------------------------- +function export_blobs(eq, cpath, iir_txt, iir_bin, fir_txt, fir_bin) +%% Two-channel blob with a single shared response. Both channels are +%% assigned to response 0, which suits identical L/R drivers on a +%% single endpoint. For endpoints with distinct per-channel tuning, +%% pass num_responses > 1 and adjust assign_response accordingly. +%% +%% iir_bin / fir_bin are relative to `cpath` (the UCM staging tree), +%% iir_txt / fir_txt are full paths to the sof-ctl text dumps under +%% tools/ctl/ipc4/eq_{iir,fir}/ and are written as-is. +channels_in_config = 2; +num_responses = 1; +assign_response = [0 0]; + +%% IIR blob +bq_iir = sof_eq_iir_blob_quant(eq.p_z, eq.p_p, eq.p_k); +bm_iir = sof_eq_iir_blob_merge(channels_in_config, num_responses, ... + assign_response, bq_iir); +bp_iir = sof_eq_iir_blob_pack(bm_iir, 4); % IPC4 +sof_alsactl_write(iir_txt, bp_iir); +sof_ucm_blob_write(fullfile(cpath, iir_bin), bp_iir); + +%% FIR blob +bq_fir = sof_eq_fir_blob_quant(eq.b_fir); +bm_fir = sof_eq_fir_blob_merge(channels_in_config, num_responses, ... + assign_response, bq_fir); +bp_fir = sof_eq_fir_blob_pack(bm_fir, 4); % IPC4 +sof_alsactl_write(fir_txt, bp_fir); +sof_ucm_blob_write(fullfile(cpath, fir_bin), bp_fir); +end + +%% ----------------------------------------------------------------------- +%% Small helpers used by the IIR stages +%% ----------------------------------------------------------------------- +function peq = shelf_row(type, p, b) +peq = [type, clamp(p(1), b(1,1), b(1,2)), clamp(p(2), b(2,1), b(2,2)), 0]; +end + +function peq = pn_row(p, b, eq) +peq = [eq.PEQ_PN2, clamp(p(1), b(1,1), b(1,2)), ... + clamp(p(2), b(2,1), b(2,2)), ... + clamp(p(3), b(3,1), b(3,2))]; +end + +function e = stage_rms(peq_fixed, new_row, eq, fmin_fit, fmax_fit) +eq.peq = [peq_fixed; new_row]; +%% Skip the FIR design pass on every fminsearch evaluation. The stage +%% objective below only looks at eq.err_db_s and eq.iir_eq_db, which +%% sof_eq_compute produces before the FIR stage, so designing the FIR +%% here would just be wasted work inside the optimization loop. +eq.enable_fir = 0; +try + eq2 = sof_eq_compute(eq); +catch + e = 1e6; + return; +end +idx = eq2.f >= fmin_fit & eq2.f <= fmax_fit; +%% eq.err_db_s is the eq-smoothed (logsmooth_eq) target-minus-measurement +%% error the FIR will pick up. What remains for the FIR after the IIR is +%% err_db_s - iir_eq_db, so minimizing its shape here fits the IIR against +%% the same signal the FIR design consumes and applies logsmooth_eq (not +%% the plot-only logsmooth_plot). +residual = eq2.err_db_s(idx) - eq2.iir_eq_db(idx); +residual = residual - mean(residual); +e = sqrt(mean(residual .^ 2)); +end + +function y = clamp(x, lo, hi) +y = min(hi, max(lo, x)); +end + +%% ----------------------------------------------------------------------- +%% UCM product .conf generation and misc string / filesystem helpers +%% ----------------------------------------------------------------------- +function write_product_conf(conf_file, sys_vendor, product_name, endpoint, base, eq) +ep_cap = capitalize(endpoint); +iir_key = sprintf('Define.PostMixer%sPlaybackIirBlob', ep_cap); +fir_key = sprintf('Define.PostMixer%sPlaybackFirBlob', ep_cap); +iir_path = sprintf('/usr/share/alsa/ucm2/blobs/sof/ipc4/eq_iir/%s_iir.bin', base); +fir_path = sprintf('/usr/share/alsa/ucm2/blobs/sof/ipc4/eq_fir/%s_fir.bin', base); + +fid = fopen(conf_file, 'w'); +if fid < 0 + error('Could not open %s for writing', conf_file); +end +fprintf(fid, '# Add bespoke %s equalizer for %s %s\n', endpoint, sys_vendor, product_name); +fprintf(fid, '#\n'); +fprintf(fid, '# This file was generated with %s.m\n', mfilename()); +fprintf(fid, '#\n'); +fprintf(fid, '# IIR is defined as parametric equalizer and FIR carries the mid-band residual\n'); +fprintf(fid, '# correction, see:\n'); +fprintf(fid, '# https://github.com/thesofproject/sof/tree/main/src/audio/eq_iir/tune\n'); +fprintf(fid, '#\n'); +fprintf(fid, '# IIR biquad stages:\n'); +fprintf(fid, '# type fc [Hz] gain [dB] Q\n'); +for i = 1:size(eq.peq, 1) + fprintf(fid, '# %-4s %8.1f %+6.2f %5.2f\n', ... + peq_short_name(eq, eq.peq(i, 1)), ... + eq.peq(i, 2), eq.peq(i, 3), eq.peq(i, 4)); +end +fprintf(fid, '#\n'); +fprintf(fid, '# FIR parameters:\n'); +fprintf(fid, '# length = %d taps\n', eq.fir_length); +fprintf(fid, '# kaiser beta = %g\n', eq.fir_beta); +fprintf(fid, '# minph = %d\n', eq.fir_minph); +fprintf(fid, '# band = [%g, %g] Hz\n', eq.fmin_fir, eq.fmax_fir); +fprintf(fid, '\n'); +fprintf(fid, '%s "%s"\n', iir_key, iir_path); +fprintf(fid, '%s "%s"\n', fir_key, fir_path); +fclose(fid); +fprintf('Wrote %s\n', conf_file); +end + +function s = sanitize_name(field_name, s) +% Lower case, replace any non-alphanumeric run with a single underscore, and +% trim leading/trailing underscores. e.g. 'Acme Ltd.' -> 'acme_ltd', +% 'Model 100' -> 'model_100'. +% +% Error out if the result is empty (input had no ASCII letters or digits, +% e.g. a non-ASCII-only DMI value). Otherwise the blob base name would +% collapse to something like 'speaker__' and every product with such a +% DMI value would land on the same blob path. +raw = s; +s = lower(s); +s = regexprep(s, '[^a-z0-9]+', '_'); +s = regexprep(s, '^_+|_+$', ''); +if isempty(s) + error(['%s "%s" has no ASCII letters or digits and would produce ' ... + 'an empty blob name component; please pass a value that ' ... + 'contains at least one [a-z0-9] character.'], field_name, raw); +end +end + +function validate_dmi_field(field_name, s) +% Reject DMI values that would escape the output tree or otherwise produce +% a bad directory / file name when used verbatim. Path separators and the +% special names '.' and '..' are refused; any other printable UTF-8 string +% is accepted because DMI content is noisy in the wild. +if ~ischar(s) || isempty(s) + error('%s must be a non-empty string', field_name); +end +if any(s == '/') || any(s == '\') + error('%s must not contain path separators: "%s"', field_name, s); +end +if strcmp(s, '.') || strcmp(s, '..') + error('%s must not be "." or "..": "%s"', field_name, s); +end +if any(s < 32) + error('%s must not contain control characters', field_name); +end +end + +function validate_endpoint(s) +% endpoint becomes part of the UCM key Define.PostMixerPlayback... +% and a component of blob file names, so restrict it to a plain letters-only +% identifier. Extend the pattern here if a new endpoint naming scheme is +% needed. +if ~ischar(s) || isempty(s) + error('endpoint must be a non-empty string'); +end +if isempty(regexp(s, '^[A-Za-z]+$', 'once')) + error('endpoint must be letters only (e.g. speaker, headphone): "%s"', s); +end +end + +function s = capitalize(s) +if isempty(s) + return; +end +s = [upper(s(1)), lower(s(2:end))]; +end + +function ensure_dir(d) +if ~exist(d, 'dir') + [ok, msg] = mkdir(d); + if ~ok + error('mkdir %s failed: %s', d, msg); + end +end +end + +function enable_common_paths(script_dir, enable) +% sof_eq_paths() addpath/rmpaths a relative '../../../../tools/tune/common' +% that is resolved against the current working directory, so it only works +% when called from src/audio/eq_iir/tune/. Do a scoped cd to script_dir so +% the (dis)enable resolves correctly regardless of the caller's CWD. +% onCleanup restores the original directory even if sof_eq_paths errors. +orig_dir = pwd(); +cleanup = onCleanup(@() cd(orig_dir)); %#ok +cd(script_dir); +sof_eq_paths(enable); +end + +function name = peq_type_name(eq, type_num) +% Look up the PEQ_* field name whose value matches type_num, using the +% constants that sof_eq_defaults() already stored on the eq struct. This +% keeps the mapping in sync with sof_eq_define_parametric_eq.m without +% duplicating the enum here. +fns = fieldnames(eq); +for k = 1:numel(fns) + if strncmp(fns{k}, 'PEQ_', 4) && isnumeric(eq.(fns{k})) && ... + isscalar(eq.(fns{k})) && eq.(fns{k}) == type_num + name = fns{k}; + return; + end +end +name = sprintf('PEQ_%d', type_num); +end + +function name = peq_short_name(eq, type_num) +% Same lookup as peq_type_name but with the 'PEQ_' prefix stripped, e.g. +% 'HP2' or 'LS2', for compact human-readable summaries in the .conf header. +name = peq_type_name(eq, type_num); +if strncmp(name, 'PEQ_', 4) + name = name(5:end); +end +end diff --git a/src/audio/google/google_hotword_detect.c b/src/audio/google/google_hotword_detect.c index f7cf44f026fd..1055f7914e1e 100644 --- a/src/audio/google/google_hotword_detect.c +++ b/src/audio/google/google_hotword_detect.c @@ -472,8 +472,7 @@ static SHARED_DATA struct comp_driver_info ghd_driver_info = { UT_STATIC void sys_comp_ghd_init(void) { - comp_register(platform_shared_get(&ghd_driver_info, - sizeof(ghd_driver_info))); + comp_register(&ghd_driver_info); } DECLARE_MODULE(sys_comp_ghd_init); diff --git a/src/audio/host-legacy.c b/src/audio/host-legacy.c index 3d62e271f518..4560959bb044 100644 --- a/src/audio/host-legacy.c +++ b/src/audio/host-legacy.c @@ -227,7 +227,9 @@ void host_common_update(struct host_data *hd, struct comp_dev *dev, uint32_t byt struct comp_buffer *sink; int ret; bool update_mailbox = false; +#if CONFIG_HOST_DMA_IPC_POSITION_UPDATES bool send_ipc = false; +#endif if (dev->direction == SOF_IPC_STREAM_PLAYBACK) { source = hd->dma_buffer; @@ -272,6 +274,7 @@ void host_common_update(struct host_data *hd, struct comp_dev *dev, uint32_t byt if (hd->cont_update_posn) update_mailbox = true; +#if CONFIG_HOST_DMA_IPC_POSITION_UPDATES /* Don't send stream position if no_stream_position == 1 */ if (!hd->no_stream_position) { hd->report_pos += bytes; @@ -291,13 +294,16 @@ void host_common_update(struct host_data *hd, struct comp_dev *dev, uint32_t byt send_ipc = true; } } +#endif if (update_mailbox) { pipeline_get_timestamp(dev->pipeline, dev, &hd->posn); mailbox_stream_write(dev->pipeline->posn_offset, &hd->posn, sizeof(hd->posn)); +#if CONFIG_HOST_DMA_IPC_POSITION_UPDATES if (send_ipc) ipc_msg_send(hd->msg, &hd->posn, false); +#endif } } @@ -553,12 +559,14 @@ int host_common_new(struct host_data *hd, struct comp_dev *dev, ipc_build_stream_posn(&hd->posn, SOF_IPC_STREAM_POSITION, config_id); +#if CONFIG_HOST_DMA_IPC_POSITION_UPDATES hd->msg = ipc_msg_init(hd->posn.rhdr.hdr.cmd, hd->posn.rhdr.hdr.size); if (!hd->msg) { comp_err(dev, "ipc_msg_init failed"); dma_put(hd->dma); return -ENOMEM; } +#endif hd->chan = NULL; hd->copy_type = COMP_COPY_NORMAL; @@ -614,7 +622,9 @@ void host_common_free(struct host_data *hd) dma_put(hd->dma); +#if CONFIG_HOST_DMA_IPC_POSITION_UPDATES ipc_msg_free(hd->msg); +#endif dma_sg_free(NULL, &hd->config.elem_array); } @@ -686,7 +696,7 @@ int host_common_params(struct host_data *hd, struct comp_dev *dev, uint32_t period_bytes; uint32_t buffer_size; uint32_t addr_align; - uint32_t align; + uint32_t align = 0; int err; /* host params always installed by pipeline IPC */ @@ -952,7 +962,9 @@ void host_common_reset(struct host_data *hd, uint16_t state) /* reset buffer pointers */ hd->local_pos = 0; +#if CONFIG_HOST_DMA_IPC_POSITION_UPDATES hd->report_pos = 0; +#endif hd->total_data_processed = 0; hd->copy_type = COMP_COPY_NORMAL; @@ -1062,8 +1074,7 @@ static SHARED_DATA struct comp_driver_info comp_host_info = { UT_STATIC void sys_comp_host_init(void) { - comp_register(platform_shared_get(&comp_host_info, - sizeof(comp_host_info))); + comp_register(&comp_host_info); } DECLARE_MODULE(sys_comp_host_init); diff --git a/src/audio/host-zephyr.c b/src/audio/host-zephyr.c index 90b0821caf5a..95f8f35a3736 100644 --- a/src/audio/host-zephyr.c +++ b/src/audio/host-zephyr.c @@ -246,7 +246,9 @@ void host_common_update(struct host_data *hd, struct comp_dev *dev, uint32_t byt struct comp_buffer *sink; int ret; bool update_mailbox = false; +#if CONFIG_HOST_DMA_IPC_POSITION_UPDATES bool send_ipc = false; +#endif if (dev->direction == SOF_IPC_STREAM_PLAYBACK) { source = hd->dma_buffer; @@ -285,6 +287,7 @@ void host_common_update(struct host_data *hd, struct comp_dev *dev, uint32_t byt if (hd->cont_update_posn) update_mailbox = true; +#if CONFIG_HOST_DMA_IPC_POSITION_UPDATES /* Don't send stream position if no_stream_position == 1 */ if (!hd->no_stream_position) { hd->report_pos += bytes; @@ -304,13 +307,16 @@ void host_common_update(struct host_data *hd, struct comp_dev *dev, uint32_t byt send_ipc = true; } } +#endif if (update_mailbox) { pipeline_get_timestamp(dev->pipeline, dev, &hd->posn); mailbox_stream_write(dev->pipeline->posn_offset, &hd->posn, sizeof(hd->posn)); +#if CONFIG_HOST_DMA_IPC_POSITION_UPDATES if (send_ipc) ipc_msg_send(hd->msg, &hd->posn, false); +#endif } } @@ -721,12 +727,14 @@ __cold int host_common_new(struct host_data *hd, struct comp_dev *dev, ipc_build_stream_posn(&hd->posn, SOF_IPC_STREAM_POSITION, config_id); +#if CONFIG_HOST_DMA_IPC_POSITION_UPDATES hd->msg = ipc_msg_init(hd->posn.rhdr.hdr.cmd, sizeof(hd->posn)); if (!hd->msg) { comp_err(dev, "ipc_msg_init failed"); sof_dma_put(hd->dma); return -ENOMEM; } +#endif hd->chan_index = -EINVAL; hd->copy_type = COMP_COPY_NORMAL; @@ -808,7 +816,9 @@ __cold void host_common_free(struct host_data *hd) sof_dma_put(hd->dma); +#if CONFIG_HOST_DMA_IPC_POSITION_UPDATES ipc_msg_free(hd->msg); +#endif dma_sg_free(hd->alloc_ctx.heap, &hd->config.elem_array); } @@ -887,7 +897,7 @@ int host_common_params(struct host_data *hd, struct comp_dev *dev, uint32_t buffer_size; uint32_t buffer_size_preferred; uint32_t addr_align; - uint32_t align; + uint32_t align = 0; int i, err; bool is_scheduling_source = dev == dev->pipeline->sched_comp; uint32_t round_up_size; @@ -1212,7 +1222,9 @@ void host_common_reset(struct host_data *hd, uint16_t state) /* reset buffer pointers */ hd->local_pos = 0; +#if CONFIG_HOST_DMA_IPC_POSITION_UPDATES hd->report_pos = 0; +#endif hd->total_data_processed = 0; hd->copy_type = COMP_COPY_NORMAL; @@ -1326,8 +1338,7 @@ static SHARED_DATA struct comp_driver_info comp_host_info = { UT_STATIC void sys_comp_host_init(void) { - comp_register(platform_shared_get(&comp_host_info, - sizeof(comp_host_info))); + comp_register(&comp_host_info); } DECLARE_MODULE(sys_comp_host_init); diff --git a/src/audio/igo_nr/igo_nr.c b/src/audio/igo_nr/igo_nr.c index e398af7d964b..f762cf86b49d 100644 --- a/src/audio/igo_nr/igo_nr.c +++ b/src/audio/igo_nr/igo_nr.c @@ -69,10 +69,10 @@ static int igo_nr_capture_s16(struct comp_data *cd, struct sof_sink *sink, int32_t frames) { - int16_t *x, *y1, *y2; - int16_t *x_start, *y_start, *x_end, *y_end; + int16_t const *x, *x_start, *x_end; + int16_t *y1, *y2, *y_start, *y_end; int16_t sample; - size_t x_size, y_size; + int x_size, y_size; size_t request_size = frames * source_get_frame_bytes(source); int sink_samples_without_wrap; int samples_without_wrap; @@ -93,12 +93,14 @@ static int igo_nr_capture_s16(struct comp_data *cd, #endif - ret = source_get_data(source, request_size, (void const **)&x, - (void const **)&x_start, &x_size); + /* use the sample-count source/sink accessors so the returned sizes are + * already in samples and need no byte-to-element conversion + */ + ret = source_get_data_s16(source, request_size, &x, &x_start, &x_size); if (ret) return ret; - ret = sink_get_buffer(sink, request_size, (void **)&y1, (void **)&y_start, &y_size); + ret = sink_get_buffer_s16(sink, request_size, &y1, &y_start, &y_size); if (ret) { source_release_data(source, 0); return ret; @@ -130,7 +132,10 @@ static int igo_nr_capture_s16(struct comp_data *cd, i++; } - x = cir_buf_wrap(x, x_start, x_end); + /* x is const (read-only source), so wrap manually instead of + * cir_buf_wrap() which takes a non-const void * + */ + x = (x >= x_end) ? x - x_size : x; y1 = cir_buf_wrap(y1, y_start, y_end); samples_left -= samples_without_wrap; } @@ -172,9 +177,9 @@ static int igo_nr_capture_s24(struct comp_data *cd, struct sof_sink *sink, int32_t frames) { - int32_t *x, *y1, *y2; - int32_t *x_start, *y_start, *x_end, *y_end; - size_t x_size, y_size; + int32_t const *x, *x_start, *x_end; + int32_t *y1, *y2, *y_start, *y_end; + int x_size, y_size; size_t request_size = frames * source_get_frame_bytes(source); int sink_samples_without_wrap; int samples_without_wrap; @@ -195,12 +200,14 @@ static int igo_nr_capture_s24(struct comp_data *cd, #endif - ret = source_get_data(source, request_size, (void const **)&x, - (void const **)&x_start, &x_size); + /* use the sample-count source/sink accessors so the returned sizes are + * already in samples and need no byte-to-element conversion + */ + ret = source_get_data_s32(source, request_size, &x, &x_start, &x_size); if (ret) return ret; - ret = sink_get_buffer(sink, request_size, (void **)&y1, (void **)&y_start, &y_size); + ret = sink_get_buffer_s32(sink, request_size, &y1, &y_start, &y_size); if (ret) { source_release_data(source, 0); return ret; @@ -231,7 +238,10 @@ static int igo_nr_capture_s24(struct comp_data *cd, i++; } - x = cir_buf_wrap(x, x_start, x_end); + /* x is const (read-only source), so wrap manually instead of + * cir_buf_wrap() which takes a non-const void * + */ + x = (x >= x_end) ? x - x_size : x; y1 = cir_buf_wrap(y1, y_start, y_end); samples_left -= samples_without_wrap; } @@ -273,9 +283,9 @@ static int igo_nr_capture_s32(struct comp_data *cd, struct sof_sink *sink, int32_t frames) { - int32_t *x, *y1, *y2; - int32_t *x_start, *y_start, *x_end, *y_end; - size_t x_size, y_size; + int32_t const *x, *x_start, *x_end; + int32_t *y1, *y2, *y_start, *y_end; + int x_size, y_size; size_t request_size = frames * source_get_frame_bytes(source); int sink_samples_without_wrap; int samples_without_wrap; @@ -296,12 +306,14 @@ static int igo_nr_capture_s32(struct comp_data *cd, #endif - ret = source_get_data(source, request_size, (void const **)&x, - (void const **)&x_start, &x_size); + /* use the sample-count source/sink accessors so the returned sizes are + * already in samples and need no byte-to-element conversion + */ + ret = source_get_data_s32(source, request_size, &x, &x_start, &x_size); if (ret) return ret; - ret = sink_get_buffer(sink, request_size, (void **)&y1, (void **)&y_start, &y_size); + ret = sink_get_buffer_s32(sink, request_size, &y1, &y_start, &y_size); if (ret) { source_release_data(source, 0); return ret; @@ -332,7 +344,10 @@ static int igo_nr_capture_s32(struct comp_data *cd, i++; } - x = cir_buf_wrap(x, x_start, x_end); + /* x is const (read-only source), so wrap manually instead of + * cir_buf_wrap() which takes a non-const void * + */ + x = (x >= x_end) ? x - x_size : x; y1 = cir_buf_wrap(y1, y_start, y_end); samples_left -= samples_without_wrap; } @@ -566,6 +581,10 @@ static int igo_nr_get_config(struct processing_module *mod, comp_info(dev, "SOF_CTRL_CMD_BINARY"); return comp_data_blob_get_cmd(cd->model_handler, cdata, fragment_size); case SOF_CTRL_CMD_SWITCH: + if (cdata->num_elems > SOF_IPC_MAX_CHANNELS) { + comp_err(dev, "num_elems %u out of range", cdata->num_elems); + return -EINVAL; + } for (j = 0; j < cdata->num_elems; j++) { cdata->chanv[j].channel = j; cdata->chanv[j].value = cd->process_enable[j]; @@ -715,17 +734,32 @@ static void igo_nr_print_config(struct processing_module *mod) static void igo_nr_set_igo_params(struct processing_module *mod) { struct comp_data *cd = module_get_private_data(mod); - struct sof_igo_nr_config *p_config = comp_get_data_blob(cd->model_handler, NULL, NULL); + size_t config_size; + struct sof_igo_nr_config *p_config = comp_get_data_blob(cd->model_handler, + &config_size, NULL); struct comp_dev *dev = mod->dev; comp_info(dev, "entry"); - igo_nr_check_config_validity(dev, cd); - if (p_config) { - comp_info(dev, "New config detected."); - cd->config = *p_config; - igo_nr_print_config(mod); + if (!p_config) + return; + + /* the whole config struct is copied below, so the blob must be at + * least that large + */ + if (config_size < sizeof(cd->config)) { + comp_err(dev, "New config too small: %zu < %zu", + config_size, sizeof(cd->config)); + return; } + + /* Adopt the host blob only when the new config is valid */ + if (igo_nr_check_config_validity(dev, cd) != 0) + return; + + comp_info(dev, "New config detected."); + cd->config = *p_config; + igo_nr_print_config(mod); } /* copy and process stream data from source to sink buffers */ diff --git a/src/audio/intel_uaol.c b/src/audio/intel_uaol.c new file mode 100644 index 000000000000..baeb4a217103 --- /dev/null +++ b/src/audio/intel_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/audio/kpb.c b/src/audio/kpb.c index 9dca05630da1..2cec91393517 100644 --- a/src/audio/kpb.c +++ b/src/audio/kpb.c @@ -1033,18 +1033,19 @@ static void kpb_micselect_copy16(struct comp_buffer *sink, { struct audio_stream *istream = &source->stream; struct audio_stream *ostream = &sink->stream; - uint16_t ch; + uint32_t ch; size_t i; AE_SETCBEGIN0(audio_stream_get_addr(ostream)); AE_SETCEND0(audio_stream_get_end_addr(ostream)); - buffer_stream_invalidate(source, size); + const size_t samples_per_chan = size / (sizeof(ae_int16) * micsel_channels); + + buffer_stream_invalidate(source, samples_per_chan * in_channels * sizeof(ae_int16)); const ae_int16 *in_ptr = audio_stream_get_rptr(istream); ae_int16x4 d16 = AE_ZERO16(); const size_t in_offset = in_channels * sizeof(ae_int16); const size_t out_offset = micsel_channels * sizeof(ae_int16); - const size_t samples_per_chan = size / (sizeof(uint16_t) * micsel_channels); ae_int16 *out_ptr; for (ch = 0; ch < micsel_channels; ch++) { @@ -1066,19 +1067,20 @@ static void kpb_micselect_copy32(struct comp_buffer *sink, { struct audio_stream *istream = &source->stream; struct audio_stream *ostream = &sink->stream; - uint16_t ch; + uint32_t ch; size_t i; AE_SETCBEGIN0(audio_stream_get_addr(ostream)); AE_SETCEND0(audio_stream_get_end_addr(ostream)); - buffer_stream_invalidate(source, size); + const size_t samples_per_chan = size / (sizeof(ae_int32) * micsel_channels); + + buffer_stream_invalidate(source, samples_per_chan * in_channels * sizeof(ae_int32)); const ae_int32 *in_ptr = audio_stream_get_rptr(istream); ae_int32x2 d32 = AE_ZERO32(); const size_t in_offset = in_channels * sizeof(ae_int32); const size_t out_offset = micsel_channels * sizeof(ae_int32); - const size_t samples_per_chan = size / (sizeof(uint32_t) * micsel_channels); ae_int32 *out_ptr; for (ch = 0; ch < micsel_channels; ch++) { @@ -1100,14 +1102,14 @@ static void kpb_micselect_copy16(struct comp_buffer *sink, { struct audio_stream *istream = &source->stream; struct audio_stream *ostream = &sink->stream; - - buffer_stream_invalidate(source, size); size_t out_samples; - uint16_t ch; + uint32_t ch; const int16_t *in_data; int16_t *out_data; - const uint32_t samples_per_chan = size / (sizeof(uint16_t) * micsel_channels); + const size_t samples_per_chan = size / (sizeof(uint16_t) * micsel_channels); + + buffer_stream_invalidate(source, samples_per_chan * in_channels * sizeof(uint16_t)); for (ch = 0; ch < micsel_channels; ch++) { out_samples = 0; @@ -1132,13 +1134,13 @@ static void kpb_micselect_copy32(struct comp_buffer *sink, { struct audio_stream *istream = &source->stream; struct audio_stream *ostream = &sink->stream; - - buffer_stream_invalidate(source, size); size_t out_samples; - uint16_t ch; + uint32_t ch; const int32_t *in_data; int32_t *out_data; - const uint32_t samples_per_chan = size / (sizeof(uint32_t) * micsel_channels); + const size_t samples_per_chan = size / (sizeof(uint32_t) * micsel_channels); + + buffer_stream_invalidate(source, samples_per_chan * in_channels * sizeof(uint32_t)); for (ch = 0; ch < micsel_channels; ch++) { out_samples = 0; @@ -1607,7 +1609,7 @@ static void kpb_init_draining(struct comp_dev *dev, struct kpb_client *cli) struct comp_data *kpb = comp_get_drvdata(dev); bool is_sink_ready = (comp_buffer_get_sink_state(kpb->host_sink) == COMP_STATE_ACTIVE); size_t sample_width = kpb->config.sampling_width; - size_t drain_req = cli->drain_req * kpb->config.channels * + size_t drain_req = (size_t)cli->drain_req * kpb->config.channels * (kpb->config.sampling_freq / 1000) * (KPB_SAMPLE_CONTAINER_SIZE(sample_width) / 8); struct history_buffer *buff = kpb->hd.c_hb; @@ -1616,7 +1618,7 @@ static void kpb_init_draining(struct comp_dev *dev, struct kpb_client *cli) size_t local_buffered; size_t drain_interval; size_t host_period_size = kpb->host_period_size; - size_t bytes_per_ms = KPB_SAMPLES_PER_MS * + size_t bytes_per_ms = (size_t)KPB_SAMPLES_PER_MS * (KPB_SAMPLE_CONTAINER_SIZE(sample_width) / 8) * kpb->config.channels; size_t period_bytes_limit; @@ -1788,7 +1790,7 @@ static void adjust_drain_interval(struct comp_data *kpb, struct draining_data *d /* average drained bytes per second */ actual_pace = (size_t)k_ms_to_cyc_ceil64(1000) / elapsed * drained; - pipeline_period = KPB_SAMPLES_PER_MS * + pipeline_period = (size_t)KPB_SAMPLES_PER_MS * (KPB_SAMPLE_CONTAINER_SIZE(dd->sample_width) / 8) * kpb->config.channels; /* desired draining pace in bytes per second */ optimal_pace = pipeline_period * KPB_DRAIN_NUM_OF_PPL_PERIODS_AT_ONCE * 1000; @@ -2394,7 +2396,7 @@ static inline bool validate_host_params(struct comp_dev *dev, */ struct comp_data *kpb = comp_get_drvdata(dev); size_t sample_width = kpb->config.sampling_width; - size_t bytes_per_ms = KPB_SAMPLES_PER_MS * + size_t bytes_per_ms = (size_t)KPB_SAMPLES_PER_MS * (KPB_SAMPLE_CONTAINER_SIZE(sample_width) / 8) * kpb->config.channels; size_t pipeline_period_size = (dev->pipeline->period / 1000) @@ -2457,12 +2459,27 @@ static int kpb_set_micselect(struct comp_dev *dev, const void *data, { const struct kpb_micselector_config *mic_sel = data; struct comp_data *kpb = comp_get_drvdata(dev); - const size_t mic_cnt = kpb->config.channels - KPB_REFERENCE_SUPPORT_CHANNELS; - const uint8_t valid_mask = KPB_COUNT_TO_BITMASK(mic_cnt); + size_t mic_cnt; + uint8_t valid_mask; size_t i; + if (max_data_size < (int)sizeof(*mic_sel)) { + comp_err(dev, "micselector payload too small: got %d, need %d", + max_data_size, (int)sizeof(*mic_sel)); + return -EINVAL; + } + + if (kpb->config.channels < KPB_REFERENCE_SUPPORT_CHANNELS || + kpb->config.channels > KPB_MAX_SUPPORTED_CHANNELS) { + comp_err(dev, "unsupported channel count %u", kpb->config.channels); + return -EINVAL; + } + + mic_cnt = kpb->config.channels - KPB_REFERENCE_SUPPORT_CHANNELS; + valid_mask = KPB_COUNT_TO_BITMASK(mic_cnt); + if ((valid_mask & mic_sel->mask) == 0) { - comp_err(dev, "error: invalid micselector bit mask"); + comp_err(dev, "invalid micselector bit mask"); return -EINVAL; } /* selected mics counter */ @@ -2470,6 +2487,10 @@ static int kpb_set_micselect(struct comp_dev *dev, const void *data, for (i = 0; i < mic_cnt; i++) { if (KPB_IS_BIT_SET(mic_sel->mask, i)) { + if (num_of_sel_mic >= ARRAY_SIZE(kpb->offsets)) { + comp_err(dev, "too many selected mics"); + return -EINVAL; + } kpb->offsets[num_of_sel_mic] = i; num_of_sel_mic++; } @@ -2540,32 +2561,42 @@ static int prepare_fmt_modules_list(struct comp_dev *kpb_dev, struct kpb_fmt_dev_list *fmt_device_list = &((struct comp_data *)comp_get_drvdata(kpb_dev))->fmt_device_list; + if (outpin_idx >= KPB_MAX_SINK_CNT) + return -EINVAL; + fmt_device_list->kpb_list_item[outpin_idx] = kpb_dev; ret = devicelist_push(&fmt_device_list->device_list[outpin_idx], &fmt_device_list->kpb_list_item[outpin_idx]); if (ret < 0) - return ret; + goto err; for (size_t mod_idx = 0; mod_idx < modules_to_prepare->number_of_modules; ++mod_idx) { uint32_t comp_id = IPC4_COMP_ID(modules_to_prepare->dev_ids[mod_idx].module_id, modules_to_prepare->dev_ids[mod_idx].instance_id); dev = ipc4_get_comp_dev(comp_id); - if (!dev) - return -EINVAL; + if (!dev) { + ret = -EINVAL; + goto err; + } struct comp_dev **new_list_item_ptr; ret = alloc_fmt_module_list_item(fmt_device_list, dev, &new_list_item_ptr); if (ret < 0) - return ret; + goto err; *new_list_item_ptr = dev; ret = devicelist_push(&fmt_device_list->device_list[outpin_idx], new_list_item_ptr); if (ret < 0) - return ret; + goto err; } return 0; + +err: + /* drop any entries pushed so far to avoid leaving a half-configured list */ + clear_fmt_modules_list(fmt_device_list, outpin_idx); + return ret; } static int clear_fmt_modules_list(struct kpb_fmt_dev_list *fmt_device_list, @@ -2667,6 +2698,15 @@ static int kpb_set_large_config(struct comp_dev *dev, uint32_t param_id, const struct kpb_task_params *cfg = (struct kpb_task_params *)data; uint32_t outpin_id = extended_param_id.part.parameter_instance; + /* payload must cover the header and all declared dev_ids[] entries */ + if (!cfg || data_offset < offsetof(struct kpb_task_params, dev_ids)) + return -EINVAL; + + if (cfg->number_of_modules > + (data_offset - offsetof(struct kpb_task_params, dev_ids)) / + sizeof(cfg->dev_ids[0])) + return -EINVAL; + return configure_fast_mode_task(dev, cfg, outpin_id); } #endif @@ -2708,8 +2748,7 @@ static SHARED_DATA struct comp_driver_info comp_kpb_info = { UT_STATIC void sys_comp_kpb_init(void) { - comp_register(platform_shared_get(&comp_kpb_info, - sizeof(comp_kpb_info))); + comp_register(&comp_kpb_info); } DECLARE_MODULE(sys_comp_kpb_init); diff --git a/src/audio/mfcc/mfcc.c b/src/audio/mfcc/mfcc.c index fc81f69c3a10..724d3d7faf06 100644 --- a/src/audio/mfcc/mfcc.c +++ b/src/audio/mfcc/mfcc.c @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include #include #include @@ -36,29 +38,39 @@ LOG_MODULE_REGISTER(mfcc, CONFIG_SOF_LOG_LEVEL); SOF_DEFINE_REG_UUID(mfcc); -__cold_rodata const struct mfcc_func_map mfcc_fm[] = { +/** \brief Source/sink API based source copy function map. */ +struct mfcc_source_func_map { + uint8_t source_fmt; + mfcc_source_func func; +}; + +__cold_rodata static const struct mfcc_source_func_map mfcc_sfm[] = { #if CONFIG_FORMAT_S16LE - {SOF_IPC_FRAME_S16_LE, mfcc_s16_default}, -#endif /* CONFIG_FORMAT_S16LE */ + {SOF_IPC_FRAME_S16_LE, mfcc_source_copy_s16}, +#endif #if CONFIG_FORMAT_S24LE - {SOF_IPC_FRAME_S24_4LE, mfcc_s24_default}, -#endif /* CONFIG_FORMAT_S24LE */ + {SOF_IPC_FRAME_S24_4LE, mfcc_source_copy_s24}, +#endif #if CONFIG_FORMAT_S32LE - {SOF_IPC_FRAME_S32_LE, mfcc_s32_default}, -#endif /* CONFIG_FORMAT_S32LE */ + {SOF_IPC_FRAME_S32_LE, mfcc_source_copy_s32}, +#endif }; -static mfcc_func mfcc_find_func(enum sof_ipc_frame source_format, - enum sof_ipc_frame sink_format, - const struct mfcc_func_map *map, - int n) +/** + * \brief Look up the source copy function for a given input format. + * + * \param[in] source_format SOF IPC frame format of the input stream. + * + * \return Pointer to the matching mfcc_source_func, or NULL if the + * format is not supported in this build. + */ +static mfcc_source_func mfcc_find_source_func(enum sof_ipc_frame source_format) { int i; - /* Find suitable processing function from map. */ - for (i = 0; i < n; i++) { - if (source_format == map[i].source) - return map[i].func; + for (i = 0; i < ARRAY_SIZE(mfcc_sfm); i++) { + if (source_format == mfcc_sfm[i].source_fmt) + return mfcc_sfm[i].func; } return NULL; @@ -68,6 +80,16 @@ static mfcc_func mfcc_find_func(enum sof_ipc_frame source_format, * End of MFCC setup code. Next the standard component methods. */ +/** + * \brief MFCC module init callback. + * + * Allocates the component private data and the configuration blob + * handler used to receive the MFCC configuration from user space. + * + * \param[in,out] mod Processing module being initialized. + * + * \return 0 on success, -ENOMEM on allocation failure. + */ static int mfcc_init(struct processing_module *mod) { struct module_data *md = &mod->priv; @@ -92,6 +114,16 @@ static int mfcc_init(struct processing_module *mod) return 0; } +/** + * \brief MFCC module free callback. + * + * Releases the IPC notification message, configuration blob handler, + * runtime buffers and the private data structure. + * + * \param[in,out] mod Processing module being freed. + * + * \return 0 on success. + */ static int mfcc_free(struct processing_module *mod) { struct mfcc_comp_data *cd = module_get_private_data(mod); @@ -105,28 +137,76 @@ static int mfcc_free(struct processing_module *mod) return 0; } - +/** + * \brief Source/sink API based process function for MFCC. + * + * Reads input audio from sof_source, runs the STFT/Mel/DCT stage, and + * delegates output formatting and commit handling to mfcc_common.c. + * + * \param[in,out] mod Processing module. + * \param[in,out] sources Source array; only sources[0] is used. + * \param[in] num_of_sources Number of sources (must be 1). + * \param[in,out] sinks Sink array; only sinks[0] is used. + * \param[in] num_of_sinks Number of sinks (must be 1). + * + * \return 0 on success, or a negative error code from the STFT or output stages. + */ static int mfcc_process(struct processing_module *mod, - struct input_stream_buffer *input_buffers, int num_input_buffers, - struct output_stream_buffer *output_buffers, int num_output_buffers) + struct sof_source **sources, int num_of_sources, + struct sof_sink **sinks, int num_of_sinks) { struct mfcc_comp_data *cd = module_get_private_data(mod); - struct audio_stream *source = input_buffers->data; - struct audio_stream *sink = output_buffers->data; - int frames = input_buffers->size; + struct comp_dev *dev = mod->dev; + struct mfcc_state *state = &cd->state; + bool pending; + size_t source_avail; + int frames; + int num_ceps; + + comp_dbg(dev, "start"); + + /* In compress mode, retry pending output first and avoid producing + * new frames until previous frame has been committed. In legacy + * mode, pending output is held in a dedicated staging buffer + * (state->out_stage) that STFT does not touch, so input processing + * can continue while the previous period is drained. + */ + pending = state->header_pending || state->out_remain > 0; + if (cd->config->compress_output && pending) + return mfcc_process_output(mod, cd, sources, sinks, 0, 0); - comp_dbg(mod->dev, "start"); + source_avail = source_get_data_frames_available(sources[0]); + frames = MIN(source_avail, cd->max_frames); + if (!frames) + return 0; - frames = MIN(frames, cd->max_frames); - cd->mfcc_func(mod, input_buffers, output_buffers, frames); + /* Copy input audio from source to MFCC internal circular buffer */ + cd->source_func(sources[0], &state->buf, &state->emph, frames, state->source_channel); - /* TODO: use module_update_buffer_position() from #6194 */ - input_buffers->consumed += audio_stream_frame_bytes(source) * frames; - output_buffers->size += audio_stream_frame_bytes(sink) * frames; - comp_dbg(mod->dev, "done"); - return 0; + /* Run STFT and Mel/DCT processing */ + num_ceps = mfcc_stft_process(mod, cd); + if (num_ceps < 0) + return num_ceps; + + return mfcc_process_output(mod, cd, sources, sinks, num_ceps, frames); } +/** + * \brief MFCC module prepare callback. + * + * Validates the source/sink connection, reads the configuration blob, + * initializes the MFCC processing state via mfcc_setup(), selects the + * input copy function for the source frame format, and prepares the + * VAD switch control notification when enabled. + * + * \param[in,out] mod Processing module being prepared. + * \param[in,out] sources Source array; only sources[0] is used. + * \param[in] num_of_sources Number of sources (must be 1). + * \param[in,out] sinks Sink array; only sinks[0] is used. + * \param[in] num_of_sinks Number of sinks (must be 1). + * + * \return 0 on success or a negative error code. + */ static int mfcc_prepare(struct processing_module *mod, struct sof_source **sources, int num_of_sources, struct sof_sink **sinks, int num_of_sinks) @@ -172,13 +252,21 @@ static int mfcc_prepare(struct processing_module *mod, return -EINVAL; } - cd->mfcc_func = mfcc_find_func(source_format, sink_format, mfcc_fm, ARRAY_SIZE(mfcc_fm)); - if (!cd->mfcc_func) { - comp_err(dev, "No proc func"); + cd->source_func = mfcc_find_source_func(source_format); + if (!cd->source_func) { + comp_err(dev, "No source func"); mfcc_free_buffers(mod); return -EINVAL; } + cd->source_format = source_format; + + if (cd->config->compress_output) + comp_info(dev, "compress PCM output mode enabled"); + + if (cd->config->enable_dtx && !cd->config->compress_output) + comp_warn(dev, "enable_dtx ignored in normal PCM mode, only applies to compress"); + /* Initialize VAD switch control notification if enabled */ if (cd->config->enable_vad && cd->config->update_controls) { if (!cd->msg) { @@ -194,6 +282,17 @@ static int mfcc_prepare(struct processing_module *mod, return 0; } +/** + * \brief MFCC module reset callback. + * + * Frees runtime buffers (NULLing their pointers) so a subsequent + * mfcc_prepare() can re-allocate cleanly. The configuration blob + * handler and IPC notification message are preserved. + * + * \param[in,out] mod Processing module being reset. + * + * \return 0 on success. + */ static int mfcc_reset(struct processing_module *mod) { struct mfcc_comp_data *cd = module_get_private_data(mod); @@ -206,7 +305,7 @@ static int mfcc_reset(struct processing_module *mod) mfcc_free_buffers(mod); /* Reset to similar state as init() */ - cd->mfcc_func = NULL; + cd->source_func = NULL; return 0; } @@ -215,7 +314,7 @@ static const struct module_interface mfcc_interface = { .free = mfcc_free, .set_configuration = mfcc_set_config, .get_configuration = mfcc_get_config, - .process_audio_stream = mfcc_process, + .process = mfcc_process, .prepare = mfcc_prepare, .reset = mfcc_reset, }; diff --git a/src/audio/mfcc/mfcc_common.c b/src/audio/mfcc/mfcc_common.c index 0e6efc2e8ba4..3890671165fd 100644 --- a/src/audio/mfcc/mfcc_common.c +++ b/src/audio/mfcc/mfcc_common.c @@ -7,7 +7,8 @@ #include #include -#include +#include +#include #include #include #include @@ -20,16 +21,262 @@ #include #include #include +#include #include LOG_MODULE_REGISTER(mfcc_common, CONFIG_SOF_LOG_LEVEL); +/* + * Source/sink API based source copy functions. + * These use sof_source API and are compiled on all platforms (generic, HiFi3, HiFi4). + */ + +#if CONFIG_FORMAT_S16LE +/** + * \brief Copy S16_LE input frames from source to MFCC circular buffer. + * + * Reads \p frames frames from \p source, selects channel \p source_channel, + * optionally applies the pre-emphasis filter \p emph, and writes Q1.15 mono + * samples to the MFCC input circular buffer \p buf. The source read is + * released after the copy completes. On source read failure the function + * returns without modifying \p buf state. + * + * \param[in,out] source Audio source providing interleaved S16_LE frames. + * \param[in,out] buf MFCC input circular buffer (mono Q1.15). + * \param[in,out] emph Pre-emphasis filter state; updated when enabled. + * \param[in] frames Number of input frames to copy. + * \param[in] source_channel Channel index to extract from the source. + */ +void mfcc_source_copy_s16(struct sof_source *source, struct mfcc_buffer *buf, + struct mfcc_pre_emph *emph, int frames, int source_channel) +{ + int16_t const *x; + int16_t const *x_start; + int16_t const *x_end; + int x_size; + int num_channels = source_get_channels(source); + size_t req_bytes = frames * num_channels * sizeof(int16_t); + int16_t *w = buf->w_ptr; + int src_without_wrap; + int dst_without_wrap; + int samples_without_wrap; + int remaining = frames; + int32_t s; + int ret; + int i; + + ret = source_get_data_s16(source, req_bytes, &x, &x_start, &x_size); + if (ret) + return; + + x += source_channel; + x_end = x_start + x_size; + + while (remaining) { + src_without_wrap = (x_end - x) / num_channels; + dst_without_wrap = buf->end_addr - w; + samples_without_wrap = MIN(src_without_wrap, dst_without_wrap); + samples_without_wrap = MIN(samples_without_wrap, remaining); + if (emph->enable) { + for (i = 0; i < samples_without_wrap; i++) { + s = (int32_t)emph->delay * emph->coef + + Q_SHIFT_LEFT(*x, 15, 30); + *w = sat_int16(Q_SHIFT_RND(s, 30, 15)); + emph->delay = *x; + x += num_channels; + w++; + } + } else { + for (i = 0; i < samples_without_wrap; i++) { + *w = *x; + x += num_channels; + w++; + } + } + x = (x >= x_end) ? x - x_size : x; + w = mfcc_buffer_wrap(buf, w); + remaining -= samples_without_wrap; + } + + buf->s_avail += frames; + buf->s_free -= frames; + buf->w_ptr = w; + source_release_data(source, req_bytes); +} +#endif /* CONFIG_FORMAT_S16LE */ + +#if CONFIG_FORMAT_S24LE +/** + * \brief Copy S24_4LE input frames from source to MFCC circular buffer. + * + * Same as mfcc_source_copy_s16() but accepts S24_4LE input (24-bit data + * sign-extended in a 32-bit container). Samples are downscaled to Q1.15 + * for the MFCC pipeline. + * + * \param[in,out] source Audio source providing interleaved S24_4LE frames. + * \param[in,out] buf MFCC input circular buffer (mono Q1.15). + * \param[in,out] emph Pre-emphasis filter state; updated when enabled. + * \param[in] frames Number of input frames to copy. + * \param[in] source_channel Channel index to extract from the source. + */ +void mfcc_source_copy_s24(struct sof_source *source, struct mfcc_buffer *buf, + struct mfcc_pre_emph *emph, int frames, int source_channel) +{ + int32_t const *x; + int32_t const *x_start; + int32_t const *x_end; + int x_size; + int num_channels = source_get_channels(source); + size_t req_bytes = frames * num_channels * sizeof(int32_t); + int16_t *w = buf->w_ptr; + int src_without_wrap; + int dst_without_wrap; + int samples_without_wrap; + int remaining = frames; + int32_t s, tmp; + int ret; + int i; + + ret = source_get_data_s32(source, req_bytes, &x, &x_start, &x_size); + if (ret) + return; + + x += source_channel; + x_end = x_start + x_size; + + while (remaining) { + src_without_wrap = (x_end - x) / num_channels; + dst_without_wrap = buf->end_addr - w; + samples_without_wrap = MIN(src_without_wrap, dst_without_wrap); + samples_without_wrap = MIN(samples_without_wrap, remaining); + if (emph->enable) { + for (i = 0; i < samples_without_wrap; i++) { + s = (int32_t)((uint32_t)*x << 8); + tmp = (int32_t)emph->delay * emph->coef + + Q_SHIFT(s, 31, 30); + *w = sat_int16(Q_SHIFT_RND(tmp, 30, 15)); + emph->delay = sat_int16(Q_SHIFT_RND(s, 31, 15)); + x += num_channels; + w++; + } + } else { + for (i = 0; i < samples_without_wrap; i++) { + s = (int32_t)((uint32_t)*x << 8); + *w = sat_int16(Q_SHIFT_RND(s, 31, 15)); + x += num_channels; + w++; + } + + } + x = (x >= x_end) ? x - x_size : x; + w = mfcc_buffer_wrap(buf, w); + remaining -= samples_without_wrap; + } + + buf->s_avail += frames; + buf->s_free -= frames; + buf->w_ptr = w; + source_release_data(source, req_bytes); +} +#endif /* CONFIG_FORMAT_S24LE */ + +#if CONFIG_FORMAT_S32LE +/** + * \brief Copy S32_LE input frames from source to MFCC circular buffer. + * + * Same as mfcc_source_copy_s16() but accepts S32_LE input. Samples are + * downscaled to Q1.15 for the MFCC pipeline. + * + * \param[in,out] source Audio source providing interleaved S32_LE frames. + * \param[in,out] buf MFCC input circular buffer (mono Q1.15). + * \param[in,out] emph Pre-emphasis filter state; updated when enabled. + * \param[in] frames Number of input frames to copy. + * \param[in] source_channel Channel index to extract from the source. + */ +void mfcc_source_copy_s32(struct sof_source *source, struct mfcc_buffer *buf, + struct mfcc_pre_emph *emph, int frames, int source_channel) +{ + int32_t const *x; + int32_t const *x_start; + int32_t const *x_end; + int x_size; + int num_channels = source_get_channels(source); + size_t req_bytes = frames * num_channels * sizeof(int32_t); + int16_t *w = buf->w_ptr; + int src_without_wrap; + int dst_without_wrap; + int samples_without_wrap; + int remaining = frames; + int32_t s; + int ret; + int i; + + ret = source_get_data_s32(source, req_bytes, &x, &x_start, &x_size); + if (ret) + return; + + x += source_channel; + x_end = x_start + x_size; + while (remaining) { + src_without_wrap = (x_end - x) / num_channels; + dst_without_wrap = buf->end_addr - w; + samples_without_wrap = MIN(src_without_wrap, dst_without_wrap); + samples_without_wrap = MIN(samples_without_wrap, remaining); + if (emph->enable) { + for (i = 0; i < samples_without_wrap; i++) { + s = (int32_t)emph->delay * emph->coef + + Q_SHIFT(*x, 31, 30); + *w = sat_int16(Q_SHIFT_RND(s, 30, 15)); + emph->delay = sat_int16(Q_SHIFT_RND(*x, 31, 15)); + x += num_channels; + w++; + } + } else { + for (i = 0; i < samples_without_wrap; i++) { + *w = sat_int16(Q_SHIFT_RND(*x, 31, 15)); + x += num_channels; + w++; + } + } + + x = (x >= x_end) ? x - x_size : x; + w = mfcc_buffer_wrap(buf, w); + remaining -= samples_without_wrap; + } + + buf->s_avail += frames; + buf->s_free -= frames; + buf->w_ptr = w; + source_release_data(source, req_bytes); +} +#endif /* CONFIG_FORMAT_S32LE */ + /* * The main processing function for MFCC */ -static int mfcc_stft_process(struct processing_module *mod, struct mfcc_comp_data *cd) +/** + * \brief Run one STFT hop and Mel/DCT processing. + * + * Waits until the input circular buffer has at least one FFT window of + * samples, then for each available hop: copies the overlap-add window + * into the FFT scratch buffer, applies the analysis window, runs the + * 32-bit FFT, computes Mel log spectra, and either keeps the Mel output + * (when \c state->mel_only is true) or applies DCT and cepstral lifter + * to produce cepstral coefficients. Optionally runs VAD on the Mel + * spectrum and sends an IPC notification on VAD state changes. + * + * \param[in,out] mod Processing module. + * \param[in,out] cd MFCC component data; state and configuration updated + * in-place. Output values land in \c state->mel_log_32 + * (Mel mode) or \c state->cepstral_coef (cepstral mode). + * + * \return Number of int32_t output values produced this call (0 if not + * enough input data was available, or no error condition was + * detected). + */ +int mfcc_stft_process(struct processing_module *mod, struct mfcc_comp_data *cd) { const struct comp_dev *dev = mod->dev; struct sof_mfcc_config *config = cd->config; @@ -45,6 +292,7 @@ static int mfcc_stft_process(struct processing_module *mod, struct mfcc_comp_dat int32_t mel_value; int32_t peak; int32_t clamp_value; + bool vad_now; /* Phase 1, wait until whole fft_size is filled with valid data. This way * first output cepstral coefficients originate from streamed data and not @@ -147,11 +395,6 @@ static int mfcc_stft_process(struct processing_module *mod, struct mfcc_comp_dat sat_int32(Q_MULTSR_32X32(s, config->mel_scale, 23, 12, 23)); } - /* Store Q9.7 version in mel_spectra for s16 output mode */ - for (j = 0; j < state->dct.num_in; j++) - state->mel_spectra->data[j] = - sat_int16(state->mel_log_32[j] >> 16); - /* Enable this to check mmax decay */ comp_dbg(dev, "state->mmax = %d", state->mmax); } else { @@ -191,7 +434,7 @@ static int mfcc_stft_process(struct processing_module *mod, struct mfcc_comp_dat /* Send notification when VAD state changes */ if (config->enable_vad && config->update_controls) { - bool vad_now = cd->vad.is_speech; + vad_now = cd->vad.is_speech; if (vad_now != cd->vad_prev) { mfcc_send_vad_notification(mod, vad_now ? 1 : 0); @@ -203,357 +446,362 @@ static int mfcc_stft_process(struct processing_module *mod, struct mfcc_comp_dat return cc_count; } -void mfcc_fill_fft_buffer(struct mfcc_state *state) +/** + * \brief Write bytes into a possibly wrapped sink buffer. + * + * Caller must ensure max_bytes <= buf_size; otherwise the wrap branch + * would write past the end of the buffer. Returns 0 on bound violation. + */ +static size_t mfcc_sink_write_bytes(uint8_t **dst, uint8_t *buf_start, + size_t buf_size, const uint8_t *src, + size_t max_bytes) { - struct mfcc_buffer *buf = &state->buf; - struct mfcc_fft *fft = &state->fft; - int32_t *d = &fft->fft_buf[fft->fft_fill_start_idx].real; - const int fft_elem_inc = sizeof(fft->fft_buf[0]) / sizeof(int32_t); - int16_t *prev = state->prev_data; - int16_t *prev_end = prev + state->prev_data_size; - int16_t *r = buf->r_ptr; - int copied; - int nmax; - int n; - int j; - - /* Copy overlapped samples from state buffer. The fft_buf has been - * cleared by caller so imaginary part remains zero. - */ - while (prev < prev_end) { - *d = *prev++; - d += fft_elem_inc; - } + uint8_t *buf_end = buf_start + buf_size; + size_t chunk; - /* Copy hop size of new data from circular buffer */ - for (copied = 0; copied < fft->fft_hop_size; copied += n) { - nmax = fft->fft_hop_size - copied; - n = mfcc_buffer_samples_without_wrap(buf, r); - n = MIN(n, nmax); - for (j = 0; j < n; j++) { - *d = *r++; - d += fft_elem_inc; - } - r = mfcc_buffer_wrap(buf, r); - } + if (max_bytes == 0) + return 0; - buf->s_avail -= copied; - buf->s_free += copied; - buf->r_ptr = r; + /* Guard: a single write must not exceed total sink buffer size. */ + if (max_bytes > buf_size) + return 0; - /* Copy for next time data back to overlap buffer */ - d = (int32_t *)&fft->fft_buf[fft->fft_fill_start_idx + fft->fft_hop_size].real; - prev = state->prev_data; - while (prev < prev_end) { - *prev++ = *d; - d += fft_elem_inc; - } -} - -#if CONFIG_FORMAT_S16LE -static int16_t *mfcc_sink_copy_zero_s16(const struct audio_stream *sink, int16_t *w_ptr, - int samples) -{ - int copied; - int nmax; - int n; - - for (copied = 0; copied < samples; copied += n) { - nmax = samples - copied; - n = audio_stream_samples_without_wrap_s16(sink, w_ptr); - n = MIN(n, nmax); - memset(w_ptr, 0, n * sizeof(int16_t)); - w_ptr = audio_stream_wrap(sink, w_ptr + n); + chunk = MIN(max_bytes, (size_t)(buf_end - *dst)); + memcpy(*dst, src, chunk); + if (chunk < max_bytes) { + memcpy(buf_start, src + chunk, max_bytes - chunk); + *dst = buf_start + (max_bytes - chunk); + } else { + *dst += chunk; + if (*dst >= buf_end) + *dst = buf_start; } - return w_ptr; + return max_bytes; } -static int16_t *mfcc_sink_copy_data_s16(const struct audio_stream *sink, int16_t *w_ptr, - int samples, int16_t *r_ptr) +/** + * \brief Prepare the next MFCC output frame after STFT processing. + * + * Copies Mel (int32 Q9.23) or cepstral (int16 Q9.7 widened to int32 Q9.23) + * output into \c state->out_stage, sets up the read pointer for staged output, + * and marks the data header as pending. + * + * \param[in,out] state MFCC state. + * \param[in] num_ceps Number of int32_t values to publish; no-op if <= 0. + */ +static void mfcc_prepare_output(struct mfcc_state *state, int num_ceps) { - int copied; - int nmax; - int n; + int k; - for (copied = 0; copied < samples; copied += n) { - nmax = samples - copied; - n = audio_stream_samples_without_wrap_s16(sink, w_ptr); - n = MIN(n, nmax); - /* Not using memcpy_s() due to speed need */ - memcpy(w_ptr, r_ptr, n * sizeof(int16_t)); - w_ptr = audio_stream_wrap(sink, w_ptr + n); - r_ptr += n; + if (num_ceps <= 0) + return; + + /* Copy into out_stage so the next STFT hop may freely reuse + * mel_log_32 / cepstral_coef while this frame is still pending. + */ + if (state->mel_only) { + for (k = 0; k < num_ceps; k++) + state->out_stage[k] = state->mel_log_32[k]; + } else { + /* Widen int16 Q9.7 cepstral coefficients to int32 Q9.23. */ + for (k = 0; k < num_ceps; k++) + state->out_stage[k] = (int32_t)state->cepstral_coef->data[k] << 16; } - return w_ptr; + state->out_data_ptr = state->out_stage; + state->out_remain = num_ceps; + state->header_pending = true; } -void mfcc_s16_default(struct processing_module *mod, struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, int frames) +/** + * \brief Commit MFCC output in compress mode. + * + * Writes the pending data header (if any) followed by the pending + * int32_t Mel/cepstral payload into the sink as a contiguous blob, + * without zero padding. When VAD + DTX are enabled, suppresses silence + * frames after a configurable trailing-silence count and optionally + * sends periodic keep-alive silence updates. + * + * \param[in,out] mod Processing module. + * \param[in,out] cd MFCC component data. + * \param[in,out] sinks Sink array; only sinks[0] is used. + * \param[in] num_ceps Number of int32_t values prepared for this hop. + * + * \return 0 on success, -ENOSPC if the sink cannot accept the frame, + * or a negative error from the sink API. + */ +static int mfcc_output_compress(struct processing_module *mod, struct mfcc_comp_data *cd, + struct sof_sink **sinks, int num_ceps) { - struct audio_stream *sink = bsink->data; - struct mfcc_comp_data *cd = module_get_private_data(mod); struct mfcc_state *state = &cd->state; - struct mfcc_buffer *buf = &cd->state.buf; - int16_t *w_ptr = audio_stream_get_wptr(sink); - const int num_header_s16 = sizeof(state->header) / sizeof(int16_t); - int num_ceps; - int sink_samples; - int to_copy; - - /* Get samples from source buffer */ - mfcc_source_copy_s16(bsource, buf, &state->emph, frames, state->source_channel); - - /* Run STFT and processing after FFT: Mel auditory filter and DCT. */ - num_ceps = mfcc_stft_process(mod, cd); + size_t out_bytes; + size_t commit_bytes; + void *sink_ptr; + void *sink_start; + size_t sink_buf_size; + uint8_t *dst; + int ret; + bool pending; + bool send_periodic; + + pending = state->header_pending || state->out_remain > 0; + if (!pending) + return 0; + + if (num_ceps > 0 && cd->config->enable_vad && !cd->vad.is_speech) { + state->vad_silence_count++; + /* With DTX enabled, send trailing silence frames + * (configurable count) then suppress. After trailing + * frames, optionally send periodic silence updates + * at the configured interval. This gives the host + * enough silence to detect end-of-speech while + * keeping alive updates during long silence. + * Without DTX, output every frame regardless of VAD. + */ + if (cd->config->enable_dtx && + state->vad_silence_count > state->dtx_trailing_silence) { + send_periodic = false; + + /* Check periodic silence frame send */ + if (state->dtx_silence_interval > 0) { + state->dtx_silence_counter++; + if (state->dtx_silence_counter >= + state->dtx_silence_interval) { + state->dtx_silence_counter = 0; + send_periodic = true; + } + } - /* If new output produced, set up pointer into scratch data and mark header pending */ - if (num_ceps > 0) { - if (state->mel_only) { - state->out_data_ptr = state->mel_spectra->data; - } else { - state->out_data_ptr = state->cepstral_coef->data; + if (!send_periodic) { + /* Suppress this frame */ + state->header_pending = false; + state->out_remain = 0; + return 0; + } } - - state->out_remain = num_ceps; - state->header_pending = true; + } else { + state->vad_silence_count = 0; + state->dtx_silence_counter = 0; } - /* Write to sink, limited by period size */ - sink_samples = frames * audio_stream_get_channels(sink); + out_bytes = (state->header_pending ? sizeof(state->header) : 0) + + state->out_remain * sizeof(int32_t); + if (out_bytes == 0) + return 0; - /* Write data header first if pending */ - if (state->header_pending) { - if (sink_samples < num_header_s16) { - /* Not enough sink space for header, defer entire frame */ - mfcc_sink_copy_zero_s16(sink, w_ptr, sink_samples); - return; - } + commit_bytes = out_bytes; + if (sink_get_free_size(sinks[0]) < commit_bytes) + return -ENOSPC; - w_ptr = mfcc_sink_copy_data_s16(sink, w_ptr, num_header_s16, - (int16_t *)&state->header); - sink_samples -= num_header_s16; - state->header_pending = false; - } + ret = sink_get_buffer(sinks[0], commit_bytes, &sink_ptr, + &sink_start, &sink_buf_size); + if (ret) + return ret; - /* Write cepstral/mel data from scratch buffer */ - to_copy = MIN(state->out_remain, sink_samples); - if (to_copy > 0) { - w_ptr = mfcc_sink_copy_data_s16(sink, w_ptr, to_copy, state->out_data_ptr); - state->out_data_ptr += to_copy; - state->out_remain -= to_copy; - sink_samples -= to_copy; + dst = sink_ptr; + if (state->header_pending) { + mfcc_sink_write_bytes(&dst, sink_start, sink_buf_size, + (uint8_t *)&state->header, + sizeof(state->header)); } - /* Zero-fill remaining sink samples */ - w_ptr = mfcc_sink_copy_zero_s16(sink, w_ptr, sink_samples); -} -#endif /* CONFIG_FORMAT_S16LE */ - -#if CONFIG_FORMAT_S24LE || CONFIG_FORMAT_S32LE -static int32_t *mfcc_sink_copy_zero_s32(const struct audio_stream *sink, int32_t *w_ptr, - int samples) -{ - int copied; - int nmax; - int n; - - for (copied = 0; copied < samples; copied += n) { - nmax = samples - copied; - n = audio_stream_samples_without_wrap_s32(sink, w_ptr); - n = MIN(n, nmax); - memset(w_ptr, 0, n * sizeof(int32_t)); - w_ptr = audio_stream_wrap(sink, w_ptr + n); + if (state->out_remain > 0) { + mfcc_sink_write_bytes(&dst, sink_start, sink_buf_size, + (uint8_t *)state->out_data_ptr, + state->out_remain * sizeof(int32_t)); } - return w_ptr; -} + ret = sink_commit_buffer(sinks[0], commit_bytes); + if (ret) + return ret; -static int32_t *mfcc_sink_copy_data_s32(const struct audio_stream *sink, int32_t *w_ptr, - int samples, int32_t *r_ptr) -{ - int copied; - int nmax; - int n; - - for (copied = 0; copied < samples; copied += n) { - nmax = samples - copied; - n = audio_stream_samples_without_wrap_s32(sink, w_ptr); - n = MIN(n, nmax); - /* Not using memcpy_s() due to speed need */ - memcpy(w_ptr, r_ptr, n * sizeof(int32_t)); - w_ptr = audio_stream_wrap(sink, w_ptr + n); - r_ptr += n; - } - - return w_ptr; + state->header_pending = false; + state->out_remain = 0; + return 0; } -#endif /* CONFIG_FORMAT_S24LE || CONFIG_FORMAT_S32LE */ -#if CONFIG_FORMAT_S24LE -void mfcc_s24_default(struct processing_module *mod, struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, int frames) +/** + * \brief Commit MFCC output in legacy PCM mode. + * + * Acquires a sink period sized as \p frames audio frames, zero-fills it, + * then writes any pending data header and pending int32_t Mel/cepstral + * payload. Any payload that does not fit in the current period stays in + * \c state->out_data_ptr / \c state->out_remain for the next call. + * + * \param[in,out] mod Processing module. + * \param[in,out] cd MFCC component data. + * \param[in,out] sources Sources array (unused; reserved for symmetry). + * \param[in,out] sinks Sink array; only sinks[0] is used. + * \param[in] frames Number of audio frames the sink expects this period. + * + * \return 0 on success, -ENOSPC if the sink cannot accept the period, + * or a negative error from the sink API. + */ +static int mfcc_output_legacy(struct processing_module *mod, struct mfcc_comp_data *cd, + struct sof_source **sources, struct sof_sink **sinks, + int frames) { - struct audio_stream *sink = bsink->data; - struct mfcc_comp_data *cd = module_get_private_data(mod); struct mfcc_state *state = &cd->state; - struct mfcc_buffer *buf = &cd->state.buf; - int32_t *w_ptr = audio_stream_get_wptr(sink); - const int num_header_s32 = sizeof(state->header) / sizeof(int32_t); - int num_ceps; - int sink_samples; - int remain_s32; - int to_copy; - int k; - - /* Get samples from source buffer */ - mfcc_source_copy_s24(bsource, buf, &state->emph, frames, state->source_channel); + size_t commit_bytes; + size_t bytes_to_end; + size_t avail; + size_t hdr_size; + size_t data_bytes; + size_t to_write; + void *sink_ptr; + void *sink_start; + size_t sink_buf_size; + uint8_t *dst; + int n32; + int ret; + + /* The MFCC sink is treated as an opaque byte container: the period + * carries an MFCC blob (header + int32 features), not PCM audio. + * Sizing the commit as sink_frame_bytes * frames keeps the period + * size matched to whatever the sink advertises (S16_LE / S24_4LE / + * S32_LE), so no format-specific conversion is needed. Any payload + * that does not fit in the current period is carried over via + * state->out_remain and drained in the next call(s). The host side + * decodes the bytes as MFCC features regardless of the sink's + * nominal PCM format, so non-S32 sinks (e.g. bench S16 topologies) + * remain supported. + */ + commit_bytes = sink_get_frame_bytes(sinks[0]) * frames; + if (sink_get_free_size(sinks[0]) < commit_bytes) + return -ENOSPC; - /* Run STFT and processing after FFT */ - num_ceps = mfcc_stft_process(mod, cd); + ret = sink_get_buffer(sinks[0], commit_bytes, &sink_ptr, + &sink_start, &sink_buf_size); + if (ret) + return ret; - /* If new output produced, set up pointer into scratch data */ - if (num_ceps > 0) { - if (state->mel_only) { - /* Convert mel_log_32 from Q9.23 to Q9.15 in-place */ - for (k = 0; k < num_ceps; k++) - state->mel_log_32[k] >>= 8; + /* Zero-fill entire period first */ + bytes_to_end = (size_t)((uint8_t *)sink_start + sink_buf_size - + (uint8_t *)sink_ptr); - state->out_data_ptr_32 = state->mel_log_32; - } else { - state->out_data_ptr = state->cepstral_coef->data; - } - - state->out_remain = num_ceps; - state->header_pending = true; + if (bytes_to_end >= commit_bytes) { + memset(sink_ptr, 0, commit_bytes); + } else { + memset(sink_ptr, 0, bytes_to_end); + memset(sink_start, 0, commit_bytes - bytes_to_end); } - /* Write to sink, limited by period size */ - sink_samples = frames * audio_stream_get_channels(sink); + dst = sink_ptr; + avail = commit_bytes; - /* Write data header first if pending */ - if (state->header_pending) { - if (sink_samples < num_header_s32) { - /* Not enough sink space for header, defer entire frame */ - mfcc_sink_copy_zero_s32(sink, w_ptr, sink_samples); - return; - } - - w_ptr = mfcc_sink_copy_data_s32(sink, w_ptr, num_header_s32, - (int32_t *)&state->header); - sink_samples -= num_header_s32; - state->header_pending = false; - } + /* Write pending header */ + if (state->header_pending && avail > 0) { + hdr_size = sizeof(state->header); - if (state->mel_only) { - /* Write 32-bit mel data Q9.15, one value per int32_t */ - to_copy = MIN(state->out_remain, sink_samples); - if (to_copy > 0) { - w_ptr = mfcc_sink_copy_data_s32(sink, w_ptr, to_copy, - state->out_data_ptr_32); - state->out_data_ptr_32 += to_copy; - state->out_remain -= to_copy; - sink_samples -= to_copy; + if (avail >= hdr_size) { + mfcc_sink_write_bytes(&dst, sink_start, sink_buf_size, + (uint8_t *)&state->header, hdr_size); + avail -= hdr_size; + state->header_pending = false; } - } else { - /* Write cepstral data packed as int32_t from scratch buffer */ - remain_s32 = (state->out_remain + 1) / 2; - to_copy = MIN(remain_s32, sink_samples); - if (to_copy > 0) { - w_ptr = mfcc_sink_copy_data_s32(sink, w_ptr, to_copy, - (int32_t *)state->out_data_ptr); - state->out_data_ptr += to_copy * 2; - state->out_remain -= to_copy * 2; - if (state->out_remain < 0) - state->out_remain = 0; + } - sink_samples -= to_copy; + /* Write pending feature data (always int32) */ + if (state->out_remain > 0 && avail > 0) { + data_bytes = state->out_remain * sizeof(int32_t); + to_write = MIN(data_bytes, avail) & ~(size_t)3; + if (to_write > 0) { + mfcc_sink_write_bytes(&dst, sink_start, sink_buf_size, + (uint8_t *)state->out_data_ptr, + to_write); + n32 = to_write / sizeof(int32_t); + state->out_data_ptr += n32; + state->out_remain -= n32; } } - /* Zero-fill remaining sink samples */ - w_ptr = mfcc_sink_copy_zero_s32(sink, w_ptr, sink_samples); + return sink_commit_buffer(sinks[0], commit_bytes); } -#endif /* CONFIG_FORMAT_S24LE */ -#if CONFIG_FORMAT_S32LE -void mfcc_s32_default(struct processing_module *mod, struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, int frames) +int mfcc_process_output(struct processing_module *mod, struct mfcc_comp_data *cd, + struct sof_source **sources, struct sof_sink **sinks, + int num_ceps, int frames) { - struct audio_stream *sink = bsink->data; - struct mfcc_comp_data *cd = module_get_private_data(mod); - struct mfcc_state *state = &cd->state; - struct mfcc_buffer *buf = &cd->state.buf; - int32_t *w_ptr = audio_stream_get_wptr(sink); - const int num_header_s32 = sizeof(state->header) / sizeof(int32_t); - int num_ceps; - int sink_samples; - int remain_s32; - int to_copy; - - /* Get samples from source buffer */ - mfcc_source_copy_s32(bsource, buf, &state->emph, frames, state->source_channel); - - /* Run STFT and processing after FFT */ - num_ceps = mfcc_stft_process(mod, cd); - - /* If new output produced, set up pointer into scratch data */ - if (num_ceps > 0) { - if (state->mel_only) { - state->out_data_ptr_32 = state->mel_log_32; - } else { - state->out_data_ptr = state->cepstral_coef->data; - } + bool pending; + + pending = cd->state.header_pending || cd->state.out_remain > 0; - state->out_remain = num_ceps; - state->header_pending = true; + if (cd->config->compress_output) { + /* Keep retrying pending frame first; don't overwrite pending state. */ + if (!pending && num_ceps > 0) + mfcc_prepare_output(&cd->state, num_ceps); + else if (pending) + num_ceps = 0; + + return mfcc_output_compress(mod, cd, sinks, num_ceps); } - /* Write to sink, limited by period size */ - sink_samples = frames * audio_stream_get_channels(sink); + /* Legacy PCM mode: same guard. Overwriting out_data_ptr / out_remain + * while a previous period's data is still pending would drop samples. + */ + if (!pending && num_ceps > 0) + mfcc_prepare_output(&cd->state, num_ceps); - /* Write data header first if pending */ - if (state->header_pending) { - if (sink_samples < num_header_s32) { - /* Not enough sink space for header, defer entire frame */ - mfcc_sink_copy_zero_s32(sink, w_ptr, sink_samples); - return; - } + return mfcc_output_legacy(mod, cd, sources, sinks, frames); +} - w_ptr = mfcc_sink_copy_data_s32(sink, w_ptr, num_header_s32, - (int32_t *)&state->header); - sink_samples -= num_header_s32; - state->header_pending = false; - } +/** + * \brief Refill the FFT input scratch from the input circular buffer. + * + * Copies the saved overlap (\c state->prev_data) into the first samples + * of \c fft->fft_buf, appends one \c fft_hop_size of new data from the + * input circular buffer (consuming it), and finally saves the trailing + * window of the FFT input back to \c state->prev_data for the next hop. + * The caller is expected to have zeroed \c fft->fft_buf so that the + * complex imaginary parts remain 0. + * + * \param[in,out] state MFCC state. Input buffer pointers/counters and + * \c prev_data are updated; \c fft->fft_buf is filled. + */ +void mfcc_fill_fft_buffer(struct mfcc_state *state) +{ + struct mfcc_buffer *buf = &state->buf; + struct mfcc_fft *fft = &state->fft; + int32_t *d = &fft->fft_buf[fft->fft_fill_start_idx].real; + const int fft_elem_inc = sizeof(fft->fft_buf[0]) / sizeof(int32_t); + int16_t *prev = state->prev_data; + int16_t *prev_end = prev + state->prev_data_size; + int16_t *r = buf->r_ptr; + int copied; + int nmax; + int n; + int j; - if (state->mel_only) { - /* Write 32-bit mel data Q9.23, one value per int32_t */ - to_copy = MIN(state->out_remain, sink_samples); - if (to_copy > 0) { - w_ptr = mfcc_sink_copy_data_s32(sink, w_ptr, to_copy, - state->out_data_ptr_32); - state->out_data_ptr_32 += to_copy; - state->out_remain -= to_copy; - sink_samples -= to_copy; - } - } else { - /* Write cepstral data packed as int32_t from scratch buffer */ - remain_s32 = (state->out_remain + 1) / 2; - to_copy = MIN(remain_s32, sink_samples); - if (to_copy > 0) { - w_ptr = mfcc_sink_copy_data_s32(sink, w_ptr, to_copy, - (int32_t *)state->out_data_ptr); - state->out_data_ptr += to_copy * 2; - state->out_remain -= to_copy * 2; - if (state->out_remain < 0) - state->out_remain = 0; + /* Copy overlapped samples from state buffer. The fft_buf has been + * cleared by caller so imaginary part remains zero. + */ + while (prev < prev_end) { + *d = *prev++; + d += fft_elem_inc; + } - sink_samples -= to_copy; + /* Copy hop size of new data from circular buffer */ + for (copied = 0; copied < fft->fft_hop_size; copied += n) { + nmax = fft->fft_hop_size - copied; + n = mfcc_buffer_samples_without_wrap(buf, r); + n = MIN(n, nmax); + for (j = 0; j < n; j++) { + *d = *r++; + d += fft_elem_inc; } + r = mfcc_buffer_wrap(buf, r); } - /* Zero-fill remaining sink samples */ - w_ptr = mfcc_sink_copy_zero_s32(sink, w_ptr, sink_samples); + buf->s_avail -= copied; + buf->s_free += copied; + buf->r_ptr = r; + + /* Copy for next time data back to overlap buffer */ + d = (int32_t *)&fft->fft_buf[fft->fft_fill_start_idx + fft->fft_hop_size].real; + prev = state->prev_data; + while (prev < prev_end) { + *prev++ = *d; + d += fft_elem_inc; + } } -#endif /* CONFIG_FORMAT_S32LE */ diff --git a/src/audio/mfcc/mfcc_generic.c b/src/audio/mfcc/mfcc_generic.c index 73ac49272ed4..65f4760a0ea3 100644 --- a/src/audio/mfcc/mfcc_generic.c +++ b/src/audio/mfcc/mfcc_generic.c @@ -8,7 +8,6 @@ #ifdef MFCC_GENERIC #include -#include #include #include #include @@ -26,6 +25,17 @@ * MFCC algorithm code */ +/** + * \brief Generic-C copy of the overlap window from the input circular buffer. + * + * Copies \p prev_data_length samples from the front of \p buf into + * \p prev_data and advances the read pointer. Used once on first frame + * to seed the overlap region for subsequent STFT hops. + * + * \param[in,out] buf Input circular buffer (Q1.15 mono). + * \param[out] prev_data Destination overlap buffer. + * \param[in] prev_data_length Number of samples to copy. + */ void mfcc_fill_prev_samples(struct mfcc_buffer *buf, int16_t *prev_data, int prev_data_length) { @@ -51,6 +61,17 @@ void mfcc_fill_prev_samples(struct mfcc_buffer *buf, int16_t *prev_data, buf->r_ptr = r; } +/** + * \brief Generic-C window function application on the FFT input buffer. + * + * Multiplies the real part of \c fft->fft_buf (sized \c fft->fft_size, + * Q1.15 input upcast to int32) by \c state->window (Q1.15) in-place and + * left-shifts by \p input_shift + 1 to produce Q1.31 fixed-point input + * to the FFT. + * + * \param[in,out] state MFCC state; \c state->fft.fft_buf is updated in-place. + * \param[in] input_shift Additional left shift applied to the windowed sample. + */ void mfcc_apply_window(struct mfcc_state *state, int input_shift) { struct mfcc_fft *fft = &state->fft; @@ -64,161 +85,4 @@ void mfcc_apply_window(struct mfcc_state *state, int input_shift) fft->fft_buf[i + j].real = (fft->fft_buf[i + j].real * state->window[j]) << s; } -#if CONFIG_FORMAT_S16LE -void mfcc_source_copy_s16(struct input_stream_buffer *bsource, struct mfcc_buffer *buf, - struct mfcc_pre_emph *emph, int frames, int source_channel) -{ - struct audio_stream *source = bsource->data; - int32_t s; - int16_t *x0; - int16_t *x = audio_stream_get_rptr(source); - int16_t *w = buf->w_ptr; - int copied; - int nmax; - int n1; - int n2; - int n; - int i; - int num_channels = audio_stream_get_channels(source); - - /* Copy from source to pre-buffer for FFT. - * The pre-emphasis filter is done in this step. - */ - for (copied = 0; copied < frames; copied += n) { - nmax = frames - copied; - n1 = audio_stream_frames_without_wrap(source, x); - n2 = mfcc_buffer_samples_without_wrap(buf, w); - n = MIN(n1, n2); - n = MIN(n, nmax); - x0 = x + source_channel; - for (i = 0; i < n; i++) { - if (emph->enable) { - /* Q1.15 x Q1.15 -> Q2.30 */ - s = (int32_t)emph->delay * emph->coef + Q_SHIFT_LEFT(*x0, 15, 30); - *w = sat_int16(Q_SHIFT_RND(s, 30, 15)); - emph->delay = *x0; - } else { - *w = *x0; - } - x0 += num_channels; - w++; - } - - x = audio_stream_wrap(source, x + n * audio_stream_get_channels(source)); - w = mfcc_buffer_wrap(buf, w); - } - buf->s_avail += copied; - buf->s_free -= copied; - buf->w_ptr = w; -} -#endif /* CONFIG_FORMAT_S16LE */ - -#if CONFIG_FORMAT_S24LE - -void mfcc_source_copy_s24(struct input_stream_buffer *bsource, struct mfcc_buffer *buf, - struct mfcc_pre_emph *emph, int frames, int source_channel) -{ - struct audio_stream *source = bsource->data; - int32_t tmp, s; - int32_t *x0; - int32_t *x = audio_stream_get_rptr(source); - int16_t *w = buf->w_ptr; - int copied; - int nmax; - int n1; - int n2; - int n; - int i; - int num_channels = audio_stream_get_channels(source); - - /* Copy from source to pre-buffer for FFT. - * The pre-emphasis filter is done in this step. - * S24_4LE data is in 32-bit container, shift left by 8 to Q1.31, - * then convert to Q1.15 with rounding. - */ - for (copied = 0; copied < frames; copied += n) { - nmax = frames - copied; - n1 = audio_stream_frames_without_wrap(source, x); - n2 = mfcc_buffer_samples_without_wrap(buf, w); - n = MIN(n1, n2); - n = MIN(n, nmax); - x0 = x + source_channel; - for (i = 0; i < n; i++) { - if (emph->enable) { - /* Convert to Q1.31, ignore highest byte */ - s = (int32_t)((uint32_t)*x0 << 8); - /* Q1.15 x Q1.15 -> Q2.30 */ - tmp = (int32_t)emph->delay * emph->coef + Q_SHIFT(s, 31, 30); - *w = sat_int16(Q_SHIFT_RND(tmp, 30, 15)); - emph->delay = sat_int16(Q_SHIFT_RND(s, 31, 15)); - } else { - /* Convert to Q1.31, ignore highest byte */ - s = (int32_t)((uint32_t)*x0 << 8); - *w = sat_int16(Q_SHIFT_RND(s, 31, 15)); - } - x0 += num_channels; - w++; - } - - x = audio_stream_wrap(source, x + n * audio_stream_get_channels(source)); - w = mfcc_buffer_wrap(buf, w); - } - buf->s_avail += copied; - buf->s_free -= copied; - buf->w_ptr = w; -} - -#endif /* CONFIG_FORMAT_S24LE */ - -#if CONFIG_FORMAT_S32LE - -void mfcc_source_copy_s32(struct input_stream_buffer *bsource, struct mfcc_buffer *buf, - struct mfcc_pre_emph *emph, int frames, int source_channel) -{ - struct audio_stream *source = bsource->data; - int32_t s; - int32_t *x0; - int32_t *x = audio_stream_get_rptr(source); - int16_t *w = buf->w_ptr; - int copied; - int nmax; - int n1; - int n2; - int n; - int i; - int num_channels = audio_stream_get_channels(source); - - /* Copy from source to pre-buffer for FFT. - * The pre-emphasis filter is done in this step. - * S32 data is in 32-bit container, shift right by 16 to get 16-bit. - */ - for (copied = 0; copied < frames; copied += n) { - nmax = frames - copied; - n1 = audio_stream_frames_without_wrap(source, x); - n2 = mfcc_buffer_samples_without_wrap(buf, w); - n = MIN(n1, n2); - n = MIN(n, nmax); - x0 = x + source_channel; - for (i = 0; i < n; i++) { - if (emph->enable) { - /* Q1.15 x Q1.15 -> Q2.30 */ - s = (int32_t)emph->delay * emph->coef + Q_SHIFT(*x0, 31, 30); - *w = sat_int16(Q_SHIFT_RND(s, 30, 15)); - emph->delay = sat_int16(Q_SHIFT_RND(*x0, 31, 15)); - } else { - *w = sat_int16(Q_SHIFT_RND(*x0, 31, 15)); - } - x0 += num_channels; - w++; - } - - x = audio_stream_wrap(source, x + n * audio_stream_get_channels(source)); - w = mfcc_buffer_wrap(buf, w); - } - buf->s_avail += copied; - buf->s_free -= copied; - buf->w_ptr = w; -} -#endif /* CONFIG_FORMAT_S32LE */ - #endif /* MFCC_GENERIC */ diff --git a/src/audio/mfcc/mfcc_hifi3.c b/src/audio/mfcc/mfcc_hifi3.c index 80c384ad6c64..fdf8e47d2deb 100644 --- a/src/audio/mfcc/mfcc_hifi3.c +++ b/src/audio/mfcc/mfcc_hifi3.c @@ -9,7 +9,6 @@ #ifdef MFCC_HIFI3 #include -#include #include #include #include @@ -35,66 +34,16 @@ static inline void set_circular_buf0(const void *start, const void *end) * MFCC algorithm code */ -#if CONFIG_FORMAT_S16LE -void mfcc_source_copy_s16(struct input_stream_buffer *bsource, struct mfcc_buffer *buf, - struct mfcc_pre_emph *emph, int frames, int source_channel) -{ - struct audio_stream *source = bsource->data; - int copied; - int nmax; - int n; - int i; - int num_channels = audio_stream_get_channels(source); - ae_int16 *in; - ae_int16 *x = (ae_int16 *)audio_stream_get_rptr(source); - ae_int16 *out = (ae_int16 *)buf->w_ptr; - ae_int16x4 sample; - ae_int32x2 temp; - ae_int16x4 coef = emph->coef; - ae_int16x4 delay; - const int in_inc = sizeof(ae_int16) * num_channels; - - /* Copy from source to pre-buffer for FFT. - * The pre-emphasis filter is done in this step. - */ - for (copied = 0; copied < frames; copied += n) { - nmax = frames - copied; - n = audio_stream_frames_without_wrap(source, x); - n = MIN(n, nmax); - nmax = mfcc_buffer_samples_without_wrap(buf, (int16_t *)out); - n = MIN(n, nmax); - in = x + source_channel; - if (emph->enable) { - delay = emph->delay; - for (i = 0; i < n; i++) { - AE_L16_XP(sample, in, in_inc); - /* Q1.15 -> Q1.31 */ - temp = AE_CVT32X2F16_10(sample); - AE_MULAF16SS_00(temp, delay, coef); - delay = sample; - sample = AE_ROUND16X4F32SSYM(temp, temp); - /* 2 = sizeof(ae_int16)*/ - AE_S16_0_IP(sample, out, 2); - } - emph->delay = delay; - - } else { - for (i = 0; i < n; i++) { - AE_L16_XP(sample, in, in_inc); - /* 2 = sizeof(ae_int16)*/ - AE_S16_0_IP(sample, out, 2); - } - } - - x = audio_stream_wrap(source, x + n * num_channels); - out = (ae_int16 *)mfcc_buffer_wrap(buf, (int16_t *)out); - } - buf->s_avail += copied; - buf->s_free -= copied; - buf->w_ptr = (int16_t *)out; -} -#endif /* CONFIG_FORMAT_S16LE */ - +/** + * \brief HiFi3 copy of the overlap window from the input circular buffer. + * + * HiFi3 implementation equivalent to the generic mfcc_fill_prev_samples(); + * uses xtensa circular addressing for the source pointer. + * + * \param[in,out] buf Input circular buffer (Q1.15 mono). + * \param[out] prev_data Destination overlap buffer. + * \param[in] prev_data_length Number of samples to copy. + */ void mfcc_fill_prev_samples(struct mfcc_buffer *buf, int16_t *prev_data, int prev_data_length) { @@ -128,6 +77,16 @@ void mfcc_fill_prev_samples(struct mfcc_buffer *buf, int16_t *prev_data, buf->r_ptr = (void *)in; /* int16_t pointer but direct cast is not possible */ } +/** + * \brief HiFi3 window function application on the FFT input buffer. + * + * HiFi3 implementation equivalent to the generic mfcc_apply_window(); + * uses fractional multiply with saturating shift to produce Q1.31 + * FFT input data. + * + * \param[in,out] state MFCC state; \c state->fft.fft_buf is updated in-place. + * \param[in] input_shift Additional left shift applied to the windowed sample. + */ void mfcc_apply_window(struct mfcc_state *state, int input_shift) { struct mfcc_fft *fft = &state->fft; @@ -152,129 +111,4 @@ void mfcc_apply_window(struct mfcc_state *state, int input_shift) } } -#if CONFIG_FORMAT_S24LE -void mfcc_source_copy_s24(struct input_stream_buffer *bsource, struct mfcc_buffer *buf, - struct mfcc_pre_emph *emph, int frames, int source_channel) -{ - struct audio_stream *source = bsource->data; - int copied; - int nmax; - int n; - int i; - int num_channels = audio_stream_get_channels(source); - ae_int32 *in; - ae_int32 *x = (ae_int32 *)audio_stream_get_rptr(source); - ae_int16 *out = (ae_int16 *)buf->w_ptr; - ae_int32x2 sample32; - ae_int16x4 sample; - ae_int32x2 temp; - ae_int16x4 coef = emph->coef; - ae_int16x4 delay; - const int in_inc = sizeof(ae_int32) * num_channels; - - for (copied = 0; copied < frames; copied += n) { - nmax = frames - copied; - n = audio_stream_frames_without_wrap(source, x); - n = MIN(n, nmax); - nmax = mfcc_buffer_samples_without_wrap(buf, (int16_t *)out); - n = MIN(n, nmax); - in = x + source_channel; - if (emph->enable) { - delay = emph->delay; - for (i = 0; i < n; i++) { - AE_L32_XP(sample32, in, in_inc); - /* Shift left by 8 to sign-extend to Q1.31 */ - sample32 = AE_SLAI32(sample32, 8); - /* Then shift right by 16 to get 16-bit */ - sample32 = AE_SRAI32(sample32, 16); - sample = AE_SAT16X4(sample32, sample32); - /* Q1.15 -> Q1.31 */ - temp = AE_CVT32X2F16_10(sample); - AE_MULAF16SS_00(temp, delay, coef); - delay = sample; - sample = AE_ROUND16X4F32SSYM(temp, temp); - AE_S16_0_IP(sample, out, 2); - } - emph->delay = delay; - } else { - for (i = 0; i < n; i++) { - AE_L32_XP(sample32, in, in_inc); - /* Shift left by 8 to sign-extend to Q1.31 */ - sample32 = AE_SLAI32(sample32, 8); - /* Then shift right by 16 to get 16-bit */ - sample32 = AE_SRAI32(sample32, 16); - sample = AE_SAT16X4(sample32, sample32); - AE_S16_0_IP(sample, out, 2); - } - } - - x = audio_stream_wrap(source, x + n * num_channels); - out = (ae_int16 *)mfcc_buffer_wrap(buf, (int16_t *)out); - } - buf->s_avail += copied; - buf->s_free -= copied; - buf->w_ptr = (int16_t *)out; -} -#endif /* CONFIG_FORMAT_S24LE */ - -#if CONFIG_FORMAT_S32LE -void mfcc_source_copy_s32(struct input_stream_buffer *bsource, struct mfcc_buffer *buf, - struct mfcc_pre_emph *emph, int frames, int source_channel) -{ - struct audio_stream *source = bsource->data; - int copied; - int nmax; - int n; - int i; - int num_channels = audio_stream_get_channels(source); - ae_int32 *in; - ae_int32 *x = (ae_int32 *)audio_stream_get_rptr(source); - ae_int16 *out = (ae_int16 *)buf->w_ptr; - ae_int32x2 sample32; - ae_int16x4 sample; - ae_int32x2 temp; - ae_int16x4 coef = emph->coef; - ae_int16x4 delay; - const int in_inc = sizeof(ae_int32) * num_channels; - - for (copied = 0; copied < frames; copied += n) { - nmax = frames - copied; - n = audio_stream_frames_without_wrap(source, x); - n = MIN(n, nmax); - nmax = mfcc_buffer_samples_without_wrap(buf, (int16_t *)out); - n = MIN(n, nmax); - in = x + source_channel; - if (emph->enable) { - delay = emph->delay; - for (i = 0; i < n; i++) { - AE_L32_XP(sample32, in, in_inc); - /* S32: shift right by 16 to get 16-bit */ - sample32 = AE_SRAI32(sample32, 16); - sample = AE_SAT16X4(sample32, sample32); - /* Q1.15 -> Q1.31 */ - temp = AE_CVT32X2F16_10(sample); - AE_MULAF16SS_00(temp, delay, coef); - delay = sample; - sample = AE_ROUND16X4F32SSYM(temp, temp); - AE_S16_0_IP(sample, out, 2); - } - emph->delay = delay; - } else { - for (i = 0; i < n; i++) { - AE_L32_XP(sample32, in, in_inc); - sample32 = AE_SRAI32(sample32, 16); - sample = AE_SAT16X4(sample32, sample32); - AE_S16_0_IP(sample, out, 2); - } - } - - x = audio_stream_wrap(source, x + n * num_channels); - out = (ae_int16 *)mfcc_buffer_wrap(buf, (int16_t *)out); - } - buf->s_avail += copied; - buf->s_free -= copied; - buf->w_ptr = (int16_t *)out; -} -#endif /* CONFIG_FORMAT_S32LE */ - #endif /* MFCC_HIFI3 */ diff --git a/src/audio/mfcc/mfcc_hifi4.c b/src/audio/mfcc/mfcc_hifi4.c index 63986870793b..e86d066b408b 100644 --- a/src/audio/mfcc/mfcc_hifi4.c +++ b/src/audio/mfcc/mfcc_hifi4.c @@ -9,7 +9,6 @@ #ifdef MFCC_HIFI4 #include -#include #include #include #include @@ -31,66 +30,21 @@ static inline void set_circular_buf0(const void *start, const void *end) AE_SETCEND0(end); } -/* Setup circular for buffer 1 */ -static inline void set_circular_buf1(const void *start, const void *end) -{ - AE_SETCBEGIN1(start); - AE_SETCEND1(end); -} - /* * MFCC algorithm code */ -#if CONFIG_FORMAT_S16LE -void mfcc_source_copy_s16(struct input_stream_buffer *bsource, struct mfcc_buffer *buf, - struct mfcc_pre_emph *emph, int frames, int source_channel) -{ - struct audio_stream *source = bsource->data; - int num_channels = audio_stream_get_channels(source); - ae_int16 *in = (ae_int16 *)source->r_ptr + source_channel; - ae_int16 *out = (ae_int16 *)buf->w_ptr; - ae_int16x4 sample; - ae_int32x2 temp; - ae_int16x4 coef; - ae_int16x4 delay; - const int in_inc = sizeof(ae_int16) * num_channels; - const int out_inc = sizeof(ae_int16); - int i; - - set_circular_buf1(buf->addr, buf->end_addr); - set_circular_buf0(source->addr, source->end_addr); - - /* Copy from source to pre-buffer for FFT. - * The pre-emphasis filter is done in this step. - */ - if (emph->enable) { - delay = emph->delay; - coef = emph->coef; - for (i = 0; i < frames; i++) { - AE_L16_XC(sample, in, in_inc); - - /* Q1.15 -> Q1.31 */ - temp = AE_CVT32X2F16_10(sample); - AE_MULAF16SS_00(temp, delay, coef); - delay = sample; - sample = AE_ROUND16X4F32SSYM(temp, temp); - AE_S16_0_XC1(sample, out, out_inc); - } - emph->delay = delay; - } else { - for (i = 0; i < frames; i++) { - AE_L16_XC(sample, in, in_inc); - AE_S16_0_XC1(sample, out, out_inc); - } - } - - buf->s_avail += frames; - buf->s_free -= frames; - buf->w_ptr = (int16_t *)out; -} -#endif /* CONFIG_FORMAT_S16LE */ - +/** + * \brief HiFi4 copy of the overlap window from the input circular buffer. + * + * HiFi4/HiFi5 implementation equivalent to the generic + * mfcc_fill_prev_samples(); uses xtensa circular addressing for the + * source pointer. + * + * \param[in,out] buf Input circular buffer (Q1.15 mono). + * \param[out] prev_data Destination overlap buffer. + * \param[in] prev_data_length Number of samples to copy. + */ void mfcc_fill_prev_samples(struct mfcc_buffer *buf, int16_t *prev_data, int prev_data_length) { @@ -124,6 +78,16 @@ void mfcc_fill_prev_samples(struct mfcc_buffer *buf, int16_t *prev_data, buf->r_ptr = (int16_t *)in; } +/** + * \brief HiFi4 window function application on the FFT input buffer. + * + * HiFi4/HiFi5 implementation equivalent to the generic + * mfcc_apply_window(); uses fractional multiply with saturating shift to + * produce Q1.31 FFT input data. + * + * \param[in,out] state MFCC state; \c state->fft.fft_buf is updated in-place. + * \param[in] input_shift Additional left shift applied to the windowed sample. + */ void mfcc_apply_window(struct mfcc_state *state, int input_shift) { struct mfcc_fft *fft = &state->fft; @@ -148,111 +112,4 @@ void mfcc_apply_window(struct mfcc_state *state, int input_shift) } } -#if CONFIG_FORMAT_S24LE -void mfcc_source_copy_s24(struct input_stream_buffer *bsource, struct mfcc_buffer *buf, - struct mfcc_pre_emph *emph, int frames, int source_channel) -{ - struct audio_stream *source = bsource->data; - int num_channels = audio_stream_get_channels(source); - ae_int32 *in = (ae_int32 *)source->r_ptr + source_channel; - ae_int16 *out = (ae_int16 *)buf->w_ptr; - ae_int32x2 sample32; - ae_int16x4 sample; - ae_int32x2 temp; - ae_int16x4 coef; - ae_int16x4 delay; - const int in_inc = sizeof(ae_int32) * num_channels; - const int out_inc = sizeof(ae_int16); - int i; - - set_circular_buf1(buf->addr, buf->end_addr); - set_circular_buf0(source->addr, source->end_addr); - - if (emph->enable) { - delay = emph->delay; - coef = emph->coef; - for (i = 0; i < frames; i++) { - AE_L32_XC(sample32, in, in_inc); - /* Shift left by 8 to sign-extend to Q1.31 */ - sample32 = AE_SLAI32(sample32, 8); - /* Then shift right by 16 to get 16-bit */ - sample32 = AE_SRAI32(sample32, 16); - sample = AE_SAT16X4(sample32, sample32); - /* Q1.15 -> Q1.31 */ - temp = AE_CVT32X2F16_10(sample); - AE_MULAF16SS_00(temp, delay, coef); - delay = sample; - sample = AE_ROUND16X4F32SSYM(temp, temp); - AE_S16_0_XC1(sample, out, out_inc); - } - emph->delay = delay; - } else { - for (i = 0; i < frames; i++) { - AE_L32_XC(sample32, in, in_inc); - /* Shift left by 8 to sign-extend to Q1.31 */ - sample32 = AE_SLAI32(sample32, 8); - /* Then shift right by 16 to get 16-bit */ - sample32 = AE_SRAI32(sample32, 16); - sample = AE_SAT16X4(sample32, sample32); - AE_S16_0_XC1(sample, out, out_inc); - } - } - - buf->s_avail += frames; - buf->s_free -= frames; - buf->w_ptr = (int16_t *)out; -} -#endif /* CONFIG_FORMAT_S24LE */ - -#if CONFIG_FORMAT_S32LE -void mfcc_source_copy_s32(struct input_stream_buffer *bsource, struct mfcc_buffer *buf, - struct mfcc_pre_emph *emph, int frames, int source_channel) -{ - struct audio_stream *source = bsource->data; - int num_channels = audio_stream_get_channels(source); - ae_int32 *in = (ae_int32 *)source->r_ptr + source_channel; - ae_int16 *out = (ae_int16 *)buf->w_ptr; - ae_int32x2 sample32; - ae_int16x4 sample; - ae_int32x2 temp; - ae_int16x4 coef; - ae_int16x4 delay; - const int in_inc = sizeof(ae_int32) * num_channels; - const int out_inc = sizeof(ae_int16); - int i; - - set_circular_buf1(buf->addr, buf->end_addr); - set_circular_buf0(source->addr, source->end_addr); - - if (emph->enable) { - delay = emph->delay; - coef = emph->coef; - for (i = 0; i < frames; i++) { - AE_L32_XC(sample32, in, in_inc); - /* S32: shift right by 16 to get 16-bit */ - sample32 = AE_SRAI32(sample32, 16); - sample = AE_SAT16X4(sample32, sample32); - /* Q1.15 -> Q1.31 */ - temp = AE_CVT32X2F16_10(sample); - AE_MULAF16SS_00(temp, delay, coef); - delay = sample; - sample = AE_ROUND16X4F32SSYM(temp, temp); - AE_S16_0_XC1(sample, out, out_inc); - } - emph->delay = delay; - } else { - for (i = 0; i < frames; i++) { - AE_L32_XC(sample32, in, in_inc); - sample32 = AE_SRAI32(sample32, 16); - sample = AE_SAT16X4(sample32, sample32); - AE_S16_0_XC1(sample, out, out_inc); - } - } - - buf->s_avail += frames; - buf->s_free -= frames; - buf->w_ptr = (int16_t *)out; -} -#endif /* CONFIG_FORMAT_S32LE */ - #endif /* MFCC_HIFI4 */ diff --git a/src/audio/mfcc/mfcc_setup.c b/src/audio/mfcc/mfcc_setup.c index 73e69f6408a8..1d062b2a5206 100644 --- a/src/audio/mfcc/mfcc_setup.c +++ b/src/audio/mfcc/mfcc_setup.c @@ -27,6 +27,13 @@ LOG_MODULE_REGISTER(mfcc_setup, CONFIG_SOF_LOG_LEVEL); +/** + * \brief Initialize an MFCC input circular buffer descriptor. + * + * \param[out] buf Circular buffer descriptor to initialize. + * \param[in] base Backing storage (Q1.15 samples). + * \param[in] size Buffer length in samples. + */ static void mfcc_init_buffer(struct mfcc_buffer *buf, int16_t *base, int size) { buf->addr = base; @@ -38,6 +45,15 @@ static void mfcc_init_buffer(struct mfcc_buffer *buf, int16_t *base, int size) buf->s_length = size; } +/** + * \brief Fill \c state->window with the configured analysis window. + * + * \param[in,out] state MFCC state; \c state->window is written, sized + * \c state->fft.fft_size and in Q1.15. + * \param[in] name Window type identifier from the MFCC configuration. + * + * \return 0 on success, -EINVAL for an unsupported window type. + */ static int mfcc_get_window(struct mfcc_state *state, enum sof_mfcc_fft_window_type name) { struct mfcc_fft *fft = &state->fft; @@ -103,6 +119,24 @@ static int mfcc_get_cepstral_lifter(struct processing_module *mod, struct mfcc_c /* TODO mfcc setup needs to use the config blob, not hard coded parameters. * Also this is a too long function. Split to STFT, Mel filter, etc. parts. */ +/** + * \brief Allocate and initialize MFCC runtime state from the config blob. + * + * Validates the supplied configuration, allocates the input circular + * buffer, overlap buffer, window vector, FFT scratch, Mel filterbank, + * DCT/cepstral lifter matrices (when \c num_ceps > 0) and VAD state + * (when enabled). All allocations are freed on any failure path. + * + * \param[in,out] mod Processing module that owns the MFCC instance. + * \param[in] max_frames Maximum input frames the pipeline will deliver + * per call; sizes the input buffer. + * \param[in] sample_rate Input sample rate in Hz; must match + * \c config->sample_frequency. + * \param[in] channels Number of channels in the input stream. + * + * \return 0 on success or a negative error code (\c -EINVAL for bad + * configuration, \c -ENOMEM for an allocation failure). + */ int mfcc_setup(struct processing_module *mod, int max_frames, int sample_rate, int channels) { struct mfcc_comp_data *cd = module_get_private_data(mod); @@ -112,6 +146,10 @@ int mfcc_setup(struct processing_module *mod, int max_frames, int sample_rate, i struct mfcc_fft *fft = &state->fft; struct psy_mel_filterbank *fb = &state->melfb; struct dct_plan_16 *dct = &state->dct; + int mel_log_32_space; + int max_out_per_hop; + int out_per_hop; + int sink_per_hop; int ret; comp_dbg(dev, "entry"); @@ -129,8 +167,9 @@ int mfcc_setup(struct processing_module *mod, int max_frames, int sample_rate, i return -EINVAL; } - if (sample_rate > MFCC_MAX_SAMPLE_RATE) { - comp_err(dev, "Sample rate %d exceeds max %d Hz", sample_rate, MFCC_MAX_SAMPLE_RATE); + if (sample_rate <= 0 || sample_rate > MFCC_MAX_SAMPLE_RATE) { + comp_err(dev, "Sample rate %d out of range (1..%d Hz)", + sample_rate, MFCC_MAX_SAMPLE_RATE); return -EINVAL; } @@ -139,6 +178,37 @@ int mfcc_setup(struct processing_module *mod, int max_frames, int sample_rate, i return -EINVAL; } + /* Validate configuration parameters up front so that runtime code + * (executed every hop in the DSP) can skip divide-by-zero / range + * checks. Any later division by these fields is safe after this. + */ + if (config->frame_length <= 0 || config->frame_shift <= 0 || + config->frame_shift > config->frame_length) { + comp_err(dev, "Illegal frame_length %d or frame_shift %d", + config->frame_length, config->frame_shift); + return -EINVAL; + } + + if (config->num_mel_bins <= 0) { + comp_err(dev, "Illegal num_mel_bins %d", config->num_mel_bins); + return -EINVAL; + } + + if (config->num_ceps < 0) { + comp_err(dev, "Illegal num_ceps %d", config->num_ceps); + return -EINVAL; + } + + /* cepstral_lifter is used as a divisor in mfcc_get_cepstral_lifter(). + * Either disabled (== 0) or strictly positive. Negative values are + * rejected to keep runtime math (every hop in DSP) free of guards. + */ + if (config->num_ceps > 0 && config->cepstral_lifter < 0) { + comp_err(dev, "Illegal cepstral_lifter %d (must be >= 0)", + config->cepstral_lifter); + return -EINVAL; + } + cd->max_frames = max_frames; state->sample_rate = sample_rate; state->low_freq = config->low_freq; @@ -271,10 +341,14 @@ int mfcc_setup(struct processing_module *mod, int max_frames, int sample_rate, i state->lifter.num_ceps = config->num_ceps; state->lifter.cepstral_lifter = config->cepstral_lifter; /* Q7.9 max 64.0*/ - ret = mfcc_get_cepstral_lifter(mod, &state->lifter); - if (ret < 0) { - comp_err(dev, "Failed cepstral lifter"); - goto free_dct_matrix; + if (state->lifter.cepstral_lifter > 0) { + ret = mfcc_get_cepstral_lifter(mod, &state->lifter); + if (ret < 0) { + comp_err(dev, "Failed cepstral lifter"); + goto free_dct_matrix; + } + } else { + state->lifter.matrix = NULL; } state->mel_only = false; @@ -311,7 +385,7 @@ int mfcc_setup(struct processing_module *mod, int max_frames, int sample_rate, i state->mel_log_32 = &state->power_spectra[fft->half_fft_size]; /* Check that mel_log_32 fits in the remaining fft_buf scratch space */ - int mel_log_32_space = (int)(fft->fft_buffer_size / sizeof(int32_t)) - fft->half_fft_size; + mel_log_32_space = (int)(fft->fft_buffer_size / sizeof(int32_t)) - fft->half_fft_size; if (config->num_mel_bins > mel_log_32_space) { comp_err(dev, "num_mel_bins %d exceeds mel_log_32 scratch space %d", @@ -331,24 +405,38 @@ int mfcc_setup(struct processing_module *mod, int max_frames, int sample_rate, i /* Allocate output buffer for multi-period output. Size allows for * current output data plus leftover from previous period. */ - int max_out_per_hop = state->mel_only ? dct->num_in : dct->num_out; + max_out_per_hop = state->mel_only ? dct->num_in : dct->num_out; /* Check that output data can be drained within the periods spanned by one * FFT hop. Each hop consumes fft_hop_size input samples and produces - * max_out_per_hop + 12 (magic header) int16_t output values. The sink provides - * at least fft_hop_size * channels int16_t samples per hop (worst case s16). + * max_out_per_hop + header int32_t output values. The sink provides + * at least fft_hop_size * channels int32_t samples per hop (worst case s32). * If output exceeds this, data accumulates and will eventually overflow. + * This check is not needed in compress output mode where only actual data + * bytes are committed without zero padding. */ - int out_per_hop = max_out_per_hop + sizeof(state->header) / sizeof(int16_t); - int sink_per_hop = fft->fft_hop_size * channels; + out_per_hop = max_out_per_hop + sizeof(state->header) / sizeof(int32_t); + sink_per_hop = fft->fft_hop_size * channels; - if (out_per_hop > sink_per_hop) { - comp_err(dev, "Output %d int16 per hop exceeds sink capacity %d (hop %d x ch %d)", + if (!config->compress_output && out_per_hop > sink_per_hop) { + comp_err(dev, "Output %d int32 per hop exceeds sink capacity %d (hop %d x ch %d)", out_per_hop, sink_per_hop, fft->fft_hop_size, channels); ret = -EINVAL; goto free_lifter; } + /* Staging buffer for pending output. Decouples committed data from + * the FFT scratch (mel_log_32) so the next STFT hop can run while a + * previous frame is still being drained across multiple periods. + */ + state->out_stage_size = max_out_per_hop; + state->out_stage = mod_zalloc(mod, max_out_per_hop * sizeof(int32_t)); + if (!state->out_stage) { + comp_err(dev, "Failed output staging buffer allocate"); + ret = -ENOMEM; + goto free_lifter; + } + /* Set initial state for STFT */ state->waiting_fill = true; state->prev_samples_valid = false; @@ -357,20 +445,26 @@ int mfcc_setup(struct processing_module *mod, int max_frames, int sample_rate, i memset(&state->header, 0, sizeof(state->header)); state->header.magic = MFCC_MAGIC; state->out_data_ptr = NULL; - state->out_data_ptr_32 = NULL; state->out_remain = 0; + state->vad_silence_count = 0; + state->dtx_trailing_silence = config->dtx_trailing_silence_hops; + state->dtx_silence_interval = config->dtx_silence_hops_interval; + state->dtx_silence_counter = 0; if (config->enable_vad) { ret = mfcc_vad_init(&cd->vad, config->num_mel_bins, sample_rate, mod); if (ret < 0) { comp_err(dev, "Failed VAD init"); - goto free_lifter; + goto free_out_stage; } } comp_dbg(dev, "done"); return 0; +free_out_stage: + mod_free(mod, state->out_stage); + free_lifter: mod_free(mod, state->lifter.matrix); @@ -396,6 +490,12 @@ int mfcc_setup(struct processing_module *mod, int max_frames, int sample_rate, i return ret; } +/** + * \brief Free a single MFCC allocation and clear the caller's pointer. + * + * \param[in,out] mod Processing module owning the allocation. + * \param[in,out] ptr Address of the pointer to free; set to NULL on return. + */ static void mfcc_free_and_null(struct processing_module *mod, void **ptr) { mod_free(mod, *ptr); @@ -405,6 +505,16 @@ static void mfcc_free_and_null(struct processing_module *mod, void **ptr) /* Free MFCC buffers to prevent leaks on reset->prepare cycles. * mfcc_free_buffers() NULLs the pointers after free. */ +/** + * \brief Release all MFCC runtime allocations. + * + * Frees FFT plan, scratch buffers, input/overlap buffers, Mel + * filterbank data, DCT matrix, cepstral lifter matrix, and VAD state. + * All freed pointers are set to NULL so the function is safe to call + * across repeated reset/prepare cycles. + * + * \param[in,out] mod Processing module that owns the MFCC instance. + */ void mfcc_free_buffers(struct processing_module *mod) { struct mfcc_comp_data *cd = module_get_private_data(mod); @@ -417,6 +527,7 @@ void mfcc_free_buffers(struct processing_module *mod) mfcc_free_and_null(mod, (void **)&cd->state.melfb.data); mfcc_free_and_null(mod, (void **)&cd->state.dct.matrix); mfcc_free_and_null(mod, (void **)&cd->state.lifter.matrix); + mfcc_free_and_null(mod, (void **)&cd->state.out_stage); mfcc_free_and_null(mod, (void **)&cd->vad.noise_floor); mfcc_free_and_null(mod, (void **)&cd->vad.weights); } diff --git a/src/audio/mfcc/tune/README.md b/src/audio/mfcc/tune/README.md index e50b74d84785..422f849b56b4 100644 --- a/src/audio/mfcc/tune/README.md +++ b/src/audio/mfcc/tune/README.md @@ -5,7 +5,7 @@ MFCC component. It's simply run in Matlab or Octave with command `setup_mfcc`. The MFCC configuration parameters can be edited from the script. -## Testbench +## Testbench run The configuration can be test run with testbench. First the test topologies need to be created with `scripts/build-tools.sh -t`. Next the testbench @@ -69,18 +69,30 @@ The 80 bands Mel output can be visualized with command: The directory contains a Python script `sof_mel_to_text_live_dsp_vad.py`. It can be used with development topologies -`sof-arl-cs42l43-l0-cs35l56-l23-mfcc.tplg` and -`sof-mtl-rt713-l0-rt1316-l12-mfcc.tplg`. It captures from default audio -device `hw:0,47` (headset microphone) Mel audio features and VAD flags. -The captured frames with detected speech are sent to Whisper speech -recognizer model for conversion to text. +`sof-arl-cs42l43-l0-cs35l56-l23-mfcc-mel-normal.tplg` and +`sof-mtl-rt713-l0-rt1316-l12-mfcc-mel-normal.tplg`. + +It captures from default audio device `hw:0,47` (headset microphone PCM). +The Mel audio features and VAD flags are packed to be compatible with a +normal PCM stream. The captured frames with detected speech are sent to the +Whisper speech recognizer model for conversion to text. + +The more efficient method for audio features capture uses the compress PCM without +continuous redundant data. Such MFCC development topologies are: + +- `sof-mtl-rt713-l0-rt1316-l12-mfcc-mel-compr.tplg` +- `sof-mtl-rt713-l0-rt1316-l12-mfcc-ceps-compr.tplg` +- `sof-arl-cs42l43-l0-cs35l56-l23-mfcc-mel-compr.tplg` +- `sof-arl-cs42l43-l0-cs35l56-l23-mfcc-ceps-compr.tplg` + +E.g. the script `sof_mel_spectrogram_compress.py` uses the mfcc-mel-compr topology version. ### Prerequisites The script needs OpenVINO. Please follow the install procedure from . -The following Python pip installs are needed into the same OpenVINO venv: +The following Python pip installs are needed into the same OpenVINO venv. ```bash pip install openvino openvino-tokenizers openvino-genai @@ -89,6 +101,9 @@ pip install transformers pip install huggingface_hub ``` +The real-time spectrogram viewers in this directory use GTK 4; their setup is described +under [Live Spectrogram Viewers](#live-spectrogram-viewers) below. + ### NPU / GPU Support The script by default runs the Whisper encoder model in the NPU. To @@ -150,3 +165,95 @@ Whisper model: whisper-medium-int4-ov (encoder: CPU, decoder: CPU) >> "Hello computer" ``` +## Live Whisper Transcription with Compress PCM + +The `sof_mel_to_text_live_compress.py` script captures Mel spectrogram +frames from a SOF compress PCM device and performs live Whisper +transcription using OpenVINO. Unlike `sof_mel_to_text_live_dsp_vad.py` +which uses `arecord`, this script reads directly from the compress PCM +device with discontinuous frames handling. + +The same OpenVINO prerequisites and pip packages apply as described above +for `sof_mel_to_text_live_dsp_vad.py`. + +```bash +# Microphone compress audio features +python3 sof_mel_to_text_live_compress.py --card 0 --device 54 --model whisper-medium-int4-ov + +# Jack In compress audio features +python3 sof_mel_to_text_live_compress.py --card 0 --device 53 --model whisper-medium-int4-ov +``` + +### Compress PCM device IDs + +The compress audio-features capture PCMs in the topology2 platform +configs use the following IDs (see +`tools/topology/topology2/platform/intel/sdw-*-audio-feature-compress.conf`): + +| Device | Name | PCM ID | +|--------|-----------------------------------|--------| +| Jack | "Jack In Compress Audio Features" | 53 | +| Mic | "Microphone Compress Audio Features" | 54 | + +The non-compress PCMs (`47` jack, `48` mic, "Audio Features") are PCM +streams intended for `arecord`/`hw:0,N` (used by +`sof_mel_to_text_live_dsp_vad.py` above) and will not work with the +compress scripts below. + +## Live Spectrogram Viewers + +These viewers are helpful for interactively checking VAD operation and +developing improvements, or for educational visualization of Mel and +cepstral-coefficient spectrograms. + +#### Additional dependencies to install + +GTK 4 and the introspection bindings are shipped by the distribution. +On Debian / Ubuntu (24.04 or newer): + +```bash +sudo apt install python3-gi gir1.2-gtk-4.0 python3-numpy +sudo apt install libgirepository-2.0-dev libcairo2-dev pkg-config \ + python3-dev gir1.2-gtk-4.0 build-essential +pip install PyGObject pycairo numpy +``` + +#### Renderer selection + +GSK auto-selects the renderer; to force the GL or Vulkan back-ends +(usually the smoothest): + +```bash +GSK_RENDERER=ngl python3 sof_mel_spectrogram_compress.py ... +GSK_RENDERER=vulkan python3 sof_mel_spectrogram_compress.py ... +``` + +### Mel Spectrogram + +The `sof_mel_spectrogram_compress.py` script captures Mel spectrogram +frames from a SOF compress PCM device and displays them as a live +scrolling spectrogram with VAD status in a GTK 4 window. This is a +lightweight viewer that does not run Whisper inference. + +```bash +# Microphone compress audio features +python3 sof_mel_spectrogram_compress.py --card 0 --device 54 --width 300 + +# Jack In compress audio features +python3 sof_mel_spectrogram_compress.py --card 0 --device 53 --width 300 +``` + +### Cepstral Spectrogram + +The `sof_ceps_spectrogram_compress.py` script is the counterpart +that displays cepstral coefficients (MFCC) instead of Mel bands. It +imports the GPU-accelerated widgets from `sof_mel_spectrogram_compress.py`, +so both files must remain in the same directory. + +```bash +# Microphone compress audio features +python3 sof_ceps_spectrogram_compress.py --card 0 --device 54 --num-ceps 13 --width 300 + +# Jack In compress audio features +python3 sof_ceps_spectrogram_compress.py --card 0 --device 53 --num-ceps 13 --width 300 +``` diff --git a/src/audio/mfcc/tune/decode_all.m b/src/audio/mfcc/tune/decode_all.m index f5c7e1a06db4..4c377bf5029a 100644 --- a/src/audio/mfcc/tune/decode_all.m +++ b/src/audio/mfcc/tune/decode_all.m @@ -6,12 +6,11 @@ num_ceps = 13; num_mel = 80; -% MFCC cepstral output files +% MFCC cepstral output files (all int32 output, Q9.23) ceps_files = {'mfcc_s16.raw', 'mfcc_s24.raw', 'mfcc_s32.raw'}; -% Mel output files with corresponding format +% Mel output files (all int32 output, Q9.23) mel_files = {'mel_s16.raw', 'mel_s24.raw', 'mel_s32.raw'}; -mel_fmts = {'s16', 's24', 's32'}; % Xtensa prefixed variants xt_ceps_files = {'xt_mfcc_s16.raw', 'xt_mfcc_s24.raw', 'xt_mfcc_s32.raw'}; @@ -19,21 +18,21 @@ all_ceps_files = [ceps_files, xt_ceps_files]; all_mel_files = [mel_files, xt_mel_files]; -all_mel_fmts = [mel_fmts, mel_fmts]; for i = 1:length(all_ceps_files) fn = all_ceps_files{i}; if exist(fn, 'file') fprintf('Decoding MFCC ceps: %s\n', fn); - [ceps, t, n, vad, energy, noise_energy, frame_num] = decode_ceps(fn, num_ceps); + [ceps, t, n, vad, energy, noise_energy, frame_num] = ... + decode_ceps(fn, num_ceps); end end for i = 1:length(all_mel_files) fn = all_mel_files{i}; - fmt = all_mel_fmts{i}; if exist(fn, 'file') fprintf('Decoding Mel: %s\n', fn); - [mel, t, n, vad, energy, noise_energy, frame_num] = decode_mel(fn, num_mel, fmt); + [mel, t, n, vad, energy, noise_energy, frame_num] = ... + decode_mel(fn, num_mel); end end diff --git a/src/audio/mfcc/tune/decode_ceps.m b/src/audio/mfcc/tune/decode_ceps.m index c094ced7c0e1..480eadea2945 100644 --- a/src/audio/mfcc/tune/decode_ceps.m +++ b/src/audio/mfcc/tune/decode_ceps.m @@ -1,9 +1,10 @@ -% [ceps, t, n, vad, energy, noise_energy, frame_number] = decode_ceps(fn, num_ceps, num_channels) +% [ceps, t, n, vad, energy, noise_energy, frame_number] = decode_ceps(fn, num_ceps, hop, num_channels) % % Input % fn - File with MFCC data in .raw or .wav format % num_ceps - number of cepstral coefficients per frame -% num_channels - needed for .raw format, omit for .wav +% hop - STFT hop in seconds, defaults to 10e-3 for 10 ms +% num_channels - needed for .raw format, omit for .wav, default 1 % % Outputs % ceps - cepstral coefficients @@ -18,39 +19,34 @@ % Copyright(c) 2022-2026 Intel Corporation. All rights reserved. function [ceps, t, n, vad, energy, noise_energy, frame_number] = ... - decode_ceps(fn, num_ceps, num_channels) + decode_ceps(fn, num_ceps, hop, num_channels) if nargin < 3 + hop = 10e-3; +end +if nargin < 4 num_channels = 1; end % MFCC stream -fs = 16e3; -qformat = 7; -magic = [25443 28006]; % ASCII 'mfcc' as int16 -num_magic = 2; % magic word is 2 x int16 +qformat = 23; % Q9.23 in int32 +magic = int32(1835426659); % 0x6D666363 as int32 +num_magic = 1; % magic word is 1 x int32 -% Load output data +% Load output data (always int32) [data, num_channels] = get_file(fn, num_channels); -idx1 = find(data == magic(1)); -idx = []; -for i = 1:length(idx1) - if data(idx1(i) + 1) == magic(2) - idx = [idx idx1(i)]; - end -end +idx = find(data == magic); if isempty(idx) error('No magic value markers found from stream'); end -period_ceps = idx(2)-idx(1); num_frames = length(idx); % Header after magic is [frame_number, reserved, energy, noise_energy, vad_flag] -% as int32 (10 int16 slots), followed by num_ceps coefficients. -payload_len = 10 + num_ceps; % 5 int32 = 10 int16, then ceps data +% as int32, followed by num_ceps coefficients (int32). +payload_len = 5 + num_ceps; % Last frame can be incomplete due to span over multiple periods last = idx(end) + num_magic + payload_len - 1; @@ -58,10 +54,6 @@ num_frames = num_frames - 1; end -t_ceps = period_ceps / num_channels / fs; -t = (0:num_frames -1) * t_ceps; -n = 1:num_ceps; - payload = zeros(payload_len, num_frames); for i = 1:num_frames i1 = idx(i) + num_magic; @@ -69,14 +61,51 @@ payload(:,i) = double(data(i1:i2)); end -% Reassemble int32 from pairs of int16 (little-endian). -% Low half must be treated as unsigned with mod() to handle negative int16. -frame_number = mod(payload(1,:), 65536) + payload(2,:) * 65536; -% payload(3:4,:) is reserved, skip -energy = (mod(payload(5,:), 65536) + payload(6,:) * 65536) / 2^23; -noise_energy = (mod(payload(7,:), 65536) + payload(8,:) * 65536) / 2^23; -vad = mod(payload(9,:), 65536) + payload(10,:) * 65536; -ceps = payload(11:payload_len, :) / 2^qformat; +frame_number = payload(1, :); +% payload(2,:) is reserved, skip +energy = payload(3, :) / 2^23; +noise_energy = payload(4, :) / 2^23; +vad = payload(5, :); +ceps = payload(6:payload_len, :) / 2^qformat; + +% Fill gaps from DTX-suppressed VAD=0 frames to create continuous timeline. +% Missing frames are filled with the minimum ceps value found in the data. +first_frame = frame_number(1); +last_frame = frame_number(end); +total_frames = last_frame - first_frame + 1; +if total_frames > num_frames + ceps_fill = min(ceps(:)); + ceps_full = ones(num_ceps, total_frames) * ceps_fill; + vad_full = zeros(1, total_frames); + energy_full = zeros(1, total_frames); + noise_energy_full = zeros(1, total_frames); + frame_number_full = first_frame:last_frame; + has_data = false(1, total_frames); + for i = 1:num_frames + fi = frame_number(i) - first_frame + 1; + ceps_full(:, fi) = ceps(:, i); + vad_full(fi) = vad(i); + energy_full(fi) = energy(i); + noise_energy_full(fi) = noise_energy(i); + has_data(fi) = true; + end + % Forward-fill gaps with last received values + for fi = 2:total_frames + if ~has_data(fi) + ceps_full(:, fi) = ceps_full(:, fi - 1); + energy_full(fi) = energy_full(fi - 1); + noise_energy_full(fi) = noise_energy_full(fi - 1); + end + end + ceps = ceps_full; + vad = vad_full; + energy = energy_full; + noise_energy = noise_energy_full; + frame_number = frame_number_full; +end + +t = (frame_number - first_frame) * hop; +n = 1:num_ceps; figure; surf(t, n, ceps, 'EdgeColor', 'none'); @@ -96,18 +125,18 @@ switch lower(ext) case '.raw' fh = fopen(fn, 'r'); - data = fread(fh, 'int16'); + data = fread(fh, 'int32'); fclose(fh); case '.wav' tmp = audioread(fn, 'native'); t = whos('tmp'); - if ~strcmp(t.class, 'int16') - error('Only 16-bit wav file format is supported'); + if ~strcmp(t.class, 'int32') + error('Expected 32-bit wav for int32 MFCC output format'); end s = size(tmp); num_channels = s(2); if num_channels > 1 - data = int16(zeros(prod(s), 1)); + data = int32(zeros(prod(s), 1)); for i = 1:num_channels data(i:num_channels:end) = tmp(:, i); end diff --git a/src/audio/mfcc/tune/decode_mel.m b/src/audio/mfcc/tune/decode_mel.m index 0b9b8d09c5a8..0aca1e35ec8d 100644 --- a/src/audio/mfcc/tune/decode_mel.m +++ b/src/audio/mfcc/tune/decode_mel.m @@ -1,10 +1,10 @@ -% [mel, t, n, vad, energy, noise_energy, frame_number] = decode_mel(fn, num_mel, fmt, num_channels) +% [mel, t, n, vad, energy, noise_energy, frame_number] = decode_mel(fn, num_mel, hop, num_channels) % % Input % fn - File with Mel data in .raw or .wav format +% hop - STFT hop in seconds, defaults to 10e-3 for 10 ms % num_mel - number of Mel coefficients per frame -% fmt - format of the Mel data ('s16', 's24', 's32') -% num_channels - needed for .raw format, omit for .wav, default 2 +% num_channels - needed for .raw format, omit for .wav, default 1 % % Outputs % mel - Mel coefficients @@ -19,57 +19,32 @@ % Copyright(c) 2026 Intel Corporation. function [mel, t, n, vad, energy, noise_energy, frame_number] = ... - decode_mel(fn, num_mel, fmt, num_channels) + decode_mel(fn, num_mel, hop, num_channels) if nargin < 3 - fmt = 's16'; + hop = 10e-3; end - if nargin < 4 - num_channels = 2; + num_channels = 1; end % MFCC stream fs = 16e3; +qformat = 23; % Q9.23 in int32 -switch fmt - case 's16' - qformat = 7; - magic = [25443 28006]; % ASCII 'mfcc' as two int16 - word_size_multiplier = 2; - case 's24' - qformat = 15; - magic = int32(1835426659); % 0x6D666363 as int32 - word_size_multiplier = 1; - case 's32' - qformat = 23; - magic = int32(1835426659); % 0x6D666363 as int32 - word_size_multiplier = 1; - otherwise - error("Use 's16', 's24', or 's32' as format."); -end - -num_magic = word_size_multiplier; % magic word is 2 x int16 or 1 x int32 -num_other_header = 5 * word_size_multiplier; % frame_number, reserved, energy, noise, vad +magic = int32(1835426659); % 0x6D666363 as int32 +num_magic = 1; % magic word is 1 x int32 +num_other_header = 5; % frame_number, reserved, energy, noise, vad (all int32) -% Load output data -[data, num_channels] = get_file(fn, num_channels, fmt); +% Load output data (always int32) +[data, num_channels] = get_file(fn, num_channels); -if strcmp(fmt, 's16') - idx1 = find(data == magic(1)); - idx = []; - for i = 1:length(idx1) - next_word = idx1(i) + 1; - if next_word <= length(data) - if data(next_word) == magic(2) - idx = [idx idx1(i)]; - end - end - end -else - idx = find(data == magic); +if isempty(data) + error('File %s is empty', fn); end +idx = find(data == magic); + if isempty(idx) error('No magic value markers found from stream'); end @@ -95,26 +70,50 @@ payload(:,i) = double(data(i1:i2)); end -if strcmp(fmt, 's16') - % Reassemble int32 from pairs of int16 (little-endian). - % Low half must be treated as unsigned with mod() to handle negative int16. - frame_number = mod(payload(1,:), 65536) + payload(2,:) * 65536; - % payload(3:4,:) is reserved, skip - energy = (mod(payload(5,:), 65536) + payload(6,:) * 65536) / 2^23; - noise_energy = (mod(payload(7,:), 65536) + payload(8,:) * 65536) / 2^23; - vad = mod(payload(9,:), 65536) + payload(10,:) * 65536; - mel = payload(11:payload_len, :) / 2^qformat; -else - frame_number = payload(1, :); - % payload(2,:) is reserved, skip - energy = payload(3, :) / 2^23; - noise_energy = payload(4, :) / 2^23; - vad = payload(5, :); - mel = payload(6:payload_len, :) / 2^qformat; +frame_number = payload(1, :); +% payload(2,:) is reserved, skip +energy = payload(3, :) / 2^23; +noise_energy = payload(4, :) / 2^23; +vad = payload(5, :); +mel = payload(6:payload_len, :) / 2^qformat; + +% Fill gaps from DTX-suppressed VAD=0 frames to create continuous timeline. +% Missing frames are filled with the minimum Mel value found in the data. +first_frame = frame_number(1); +last_frame = frame_number(end); +total_frames = last_frame - first_frame + 1; +if total_frames > num_frames + mel_fill = min(mel(:)); + mel_full = ones(num_mel, total_frames) * mel_fill; + vad_full = zeros(1, total_frames); + energy_full = zeros(1, total_frames); + noise_energy_full = zeros(1, total_frames); + frame_number_full = first_frame:last_frame; + has_data = false(1, total_frames); + for i = 1:num_frames + fi = frame_number(i) - first_frame + 1; + mel_full(:, fi) = mel(:, i); + vad_full(fi) = vad(i); + energy_full(fi) = energy(i); + noise_energy_full(fi) = noise_energy(i); + has_data(fi) = true; + end + % Forward-fill gaps with last received values + for fi = 2:total_frames + if ~has_data(fi) + mel_full(:, fi) = mel_full(:, fi - 1); + energy_full(fi) = energy_full(fi - 1); + noise_energy_full(fi) = noise_energy_full(fi - 1); + end + end + mel = mel_full; + vad = vad_full; + energy = energy_full; + noise_energy = noise_energy_full; + frame_number = frame_number_full; end -t_mel = period_mel / num_channels / fs; -t = (0:num_frames -1) * t_mel; +t = (frame_number - first_frame) * hop; n = 1:num_mel; figure @@ -145,36 +144,20 @@ end -function [data, num_channels] = get_file(fn, num_channels, fmt) +function [data, num_channels] = get_file(fn, num_channels) [~, ~, ext] = fileparts(fn); -switch fmt - case 's16' - read_fmt = 'int16'; - case {'s24', 's32'} - read_fmt = 'int32'; - otherwise - error("Use 's16', 's24', or 's32' as format."); -end - switch lower(ext) case '.raw' fh = fopen(fn, 'r'); - data = fread(fh, read_fmt); + data = fread(fh, 'int32'); fclose(fh); case '.wav' tmp = audioread(fn, 'native'); t = whos('tmp'); - switch fmt - case 's16' - if ~strcmp(t.class, 'int16') - error('Expected 16-bit wav for s16 format'); - end - case {'s24', 's32'} - if ~strcmp(t.class, 'int32') - error('Expected 32-bit wav for %s format', fmt); - end + if ~strcmp(t.class, 'int32') + error('Expected 32-bit wav for int32 MFCC output format'); end s = size(tmp); num_channels = s(2); diff --git a/src/audio/mfcc/tune/setup_mfcc.m b/src/audio/mfcc/tune/setup_mfcc.m index 3cda3221a4fc..dbf69587a74f 100644 --- a/src/audio/mfcc/tune/setup_mfcc.m +++ b/src/audio/mfcc/tune/setup_mfcc.m @@ -25,6 +25,32 @@ function setup_mfcc() setup.tplg_fn = 'mel80.conf'; export_mfcc_setup(gen_cfg, setup); + % Blob for mel spectrogram with compress PCM output + setup = get_mel_spectrogram_config(); + setup.compress_output = true; + setup.tplg_fn = 'mel80_compress.conf'; + export_mfcc_setup(gen_cfg, setup); + + % Blob for mel spectrogram with compress PCM output and DTX + setup = get_mel_spectrogram_config(); + setup.compress_output = true; + setup.enable_dtx = true; + setup.dtx_trailing_silence_hops = 20; + setup.dtx_silence_hops_interval = 500; + setup.tplg_fn = 'mel80_compress_dtx.conf'; + export_mfcc_setup(gen_cfg, setup); + + % Default MFCC (cepstral) with compress PCM output + setup = get_mfcc_default_config(); + setup.compress_output = true; + setup.enable_vad = true; + setup.enable_dtx = true; + setup.dtx_trailing_silence_hops = 20; + setup.dtx_silence_hops_interval = 500; + setup.update_controls = true; + setup.tplg_fn = 'ceps13_compress_dtx.conf'; + export_mfcc_setup(gen_cfg, setup); + end function cfg = get_mfcc_default_config() @@ -64,7 +90,10 @@ function setup_mfcc() cfg.dynamic_mmax = false; % same cfg.enable_vad = false; cfg.enable_dtx = false; + cfg.dtx_trailing_silence_hops = 0; + cfg.dtx_silence_hops_interval = 0; cfg.update_controls = false; + cfg.compress_output = false; end function cfg = get_mel_spectrogram_config() @@ -104,7 +133,10 @@ function setup_mfcc() cfg.dynamic_mmax = true; cfg.enable_vad = true; cfg.enable_dtx = false; + cfg.dtx_trailing_silence_hops = 0; + cfg.dtx_silence_hops_interval = 0; cfg.update_controls = true; + cfg.compress_output = false; end function export_mfcc_setup(gen_cfg, cfg) @@ -139,8 +171,10 @@ function export_mfcc_setup(gen_cfg, cfg) v = q_convert(cfg.mmax_init, 7); [b8, j] = add_w16b(v, b8, j); v = q_convert(cfg.mmax_coef, 15); [b8, j] = add_w16b(v, b8, j); +v = cfg.dtx_trailing_silence_hops; [b8, j] = add_w16b(v, b8, j); % DTX trailing silence hops +v = cfg.dtx_silence_hops_interval; [b8, j] = add_w16b(v, b8, j); % DTX silence frame interval % Reserved -for i = 1:6 +for i = 1:5 [b8, j] = add_w32b(0, b8, j); end @@ -181,8 +215,9 @@ function export_mfcc_setup(gen_cfg, cfg) v = cfg.enable_vad; [b8, j] = add_w8b(v, b8, j); % bool v = cfg.enable_dtx; [b8, j] = add_w8b(v, b8, j); % bool v = cfg.update_controls; [b8, j] = add_w8b(v, b8, j); % bool -% reserved_bool[5] -for i = 1:5 +v = cfg.compress_output; [b8, j] = add_w8b(v, b8, j); % bool +% reserved_bool[4] +for i = 1:4 [b8, j] = add_w8b(0, b8, j); end diff --git a/src/audio/mfcc/tune/sof_ceps_spectrogram_compress.py b/src/audio/mfcc/tune/sof_ceps_spectrogram_compress.py new file mode 100755 index 000000000000..8cb8190c56b4 --- /dev/null +++ b/src/audio/mfcc/tune/sof_ceps_spectrogram_compress.py @@ -0,0 +1,374 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: BSD-3-Clause +# +# Copyright (c) 2026, Intel Corporation. + +"""Live scrolling cepstral coefficient viewer for SOF compress PCM capture. + +GTK 4 application that displays a live scrolling MFCC cepstral +spectrogram with VAD status from a SOF compress audio-features PCM. +Rendering goes through ``Gtk.Snapshot`` with ``Gdk.MemoryTexture`` for +the spectrogram and native GSK render nodes (``Gsk.Path`` strokes, +``append_color``, ``append_layout``) for the strip plots, so the +GL/Vulkan renderer handles compositing on the GPU. Vsync-aligned +redraws (``add_tick_callback``) and sub-pixel scrolling keep the 100 Hz +data stream looking smooth on a 60+ Hz display. + +Dependencies (Debian/Ubuntu): + sudo apt install python3-gi gir1.2-gtk-4.0 + pip install numpy + +Frame format: [magic(int32), frame_number(uint32), reserved(int32), + energy(int32), noise_energy(int32), vad_flag(int32), + ceps[0..N-1](int32)] + +Cepstral coefficients are in Q9.23 fixed-point format. + +Usage: + python sof_ceps_spectrogram_compress.py [--card 0] [--device 54] + python sof_ceps_spectrogram_compress.py --num-ceps 13 --width 300 +""" + +import argparse +import os +import queue +import signal +import struct +import subprocess +import sys +import threading +import time + +import numpy as np + +import gi +gi.require_version('Gtk', '4.0') +gi.require_version('Gdk', '4.0') +gi.require_version('Gsk', '4.0') +gi.require_version('Graphene', '1.0') +gi.require_version('Pango', '1.0') +from gi.repository import GLib, Gtk # noqa: E402 + +# Reuse the GPU-accelerated widgets from the mel viewer. +from sof_mel_spectrogram_compress import ( # noqa: E402 + MelSpectrogramArea as SpectrogramArea, + CurveStripArea, + GUI_REFRESH_MS, +) + +# SOF compress frame format constants (with DSP data header) +SOF_MAGIC_BYTES = struct.pack(' 3: + del buf[:-3] + return None, None, None, None, None + end = idx + frame_bytes + if end > len(buf): + del buf[:idx] + return None, None, None, None, None + + frame_number = struct.unpack_from('= 64: + break + + if self._last_frame_mono is None: + sub = 0.0 + else: + sub = (time.monotonic() - self._last_frame_mono) / self._frame_period + if sub < 0.0: + sub = 0.0 + elif sub > 1.0: + sub = 1.0 + self.ceps_area.set_sub_offset(sub) + self.vad_area.set_sub_offset(sub) + self.level_area.set_sub_offset(sub) + + if eof: + self._on_eof() + return False + return True + + def _on_tick_clock(self, _widget, _frame_clock): + return True if self._drain_and_redraw() else False + + def _on_timer(self): + return GLib.SOURCE_CONTINUE if self._drain_and_redraw() \ + else GLib.SOURCE_REMOVE + + def _ingest(self, frame_number, vad_flag, energy, noise, ceps_ints): + self.recv_frames += 1 + self._last_frame_mono = time.monotonic() + ceps = decode_ceps_frame(ceps_ints) + speech = vad_flag != 0 + + if speech != self.prev_speech: + t = frame_number * SOF_HOP_SEC + tag = "SPEECH" if speech else "SILENCE" + print(f" [{t:7.2f}s] {tag} (hop {frame_number})", flush=True) + self.prev_speech = speech + + # Scroll left and append new frame on the right. + self.ceps_buf[:-1] = self.ceps_buf[1:] + self.ceps_buf[-1] = ceps + self.vad_buf[:-1] = self.vad_buf[1:] + self.vad_buf[-1] = 1.0 if speech else 0.0 + self.energy_buf[:-1] = self.energy_buf[1:] + self.energy_buf[-1] = energy + self.noise_buf[:-1] = self.noise_buf[1:] + self.noise_buf[-1] = noise + + def _on_eof(self): + if self._closing: + return + if self.proc.poll() is None: + self.proc.terminate() + try: + rc = self.proc.wait(timeout=2) + except subprocess.TimeoutExpired: + self.proc.kill() + rc = self.proc.wait() + try: + stderr_out = self.proc.stderr.read().decode(errors='replace') + except Exception: + stderr_out = '' + print(f"\ncrecord exited with code {rc}") + if stderr_out: + print(f"stderr: {stderr_out}") + self.window.close() + + # --- shutdown ----------------------------------------------------- + def _on_close(self, *_args): + self._closing = True + if self._tick_id: + self.ceps_area.remove_tick_callback(self._tick_id) + self._tick_id = 0 + if self._timer_id: + GLib.source_remove(self._timer_id) + self._timer_id = 0 + if self.proc.poll() is None: + self.proc.terminate() + try: + self.proc.wait(timeout=3) + except subprocess.TimeoutExpired: + self.proc.kill() + self.proc.wait() + print(f"\nCapture stopped. Received {self.recv_frames} frames.") + return False + + +def main(): + parser = argparse.ArgumentParser( + description="Live scrolling MFCC cepstral coefficient viewer " + "from SOF compress PCM capture (GTK 4 / GSK)") + parser.add_argument('--card', '-c', type=int, default=0, + help='ALSA card number (default: 0)') + parser.add_argument('--device', '-d', type=int, default=54, + help='ALSA compress device number (default: 54)') + parser.add_argument('--width', '-w', type=int, default=SPECTROGRAM_WIDTH, + help=f'Spectrogram width in frames (default: {SPECTROGRAM_WIDTH})') + parser.add_argument('--num-ceps', '-n', type=int, default=DEFAULT_NUM_CEPS, + help=f'Number of cepstral coefficients (default: {DEFAULT_NUM_CEPS})') + args = parser.parse_args() + + print("=== SOF MFCC Cepstral Coefficient Viewer (Compress PCM, GTK 4) ===\n") + + signal.signal(signal.SIGINT, signal.SIG_DFL) + + app = Gtk.Application(application_id='org.sof.ceps_spectrogram') + holder = {} + + def on_activate(application): + viewer = CepsViewer(application, args.card, args.device, + args.width, args.num_ceps) + viewer.window.present() + holder['viewer'] = viewer + + app.connect('activate', on_activate) + sys.exit(app.run(None)) + + +if __name__ == '__main__': + main() diff --git a/src/audio/mfcc/tune/sof_mel_spectrogram_compress.py b/src/audio/mfcc/tune/sof_mel_spectrogram_compress.py new file mode 100755 index 000000000000..e85e0494cf22 --- /dev/null +++ b/src/audio/mfcc/tune/sof_mel_spectrogram_compress.py @@ -0,0 +1,625 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: BSD-3-Clause +# +# Copyright (c) 2026, Intel Corporation. + +"""Live scrolling mel spectrogram viewer for SOF compress PCM capture. + +GTK 4 application that displays a live scrolling mel spectrogram with +VAD status from a SOF compress audio-features PCM. Rendering uses +``Gtk.Snapshot`` with a ``Gdk.MemoryTexture`` for the spectrogram so +that the per-frame upload is handed to the GSK GPU renderer; the small +VAD / level strips use Cairo snapshot nodes. + +Dependencies (Debian/Ubuntu): + sudo apt install python3-gi gir1.2-gtk-4.0 + pip install numpy + +Frame format: [magic(int32), frame_number(uint32), reserved(int32), + energy(int32), noise_energy(int32), vad_flag(int32), + mel[0..79](int32)] + +Usage: + python sof_mel_spectrogram_compress.py [--card 0] [--device 54] + python sof_mel_spectrogram_compress.py --width 400 +""" + +import argparse +import os +import queue +import signal +import struct +import subprocess +import sys +import threading +import time + +import numpy as np + +import gi +gi.require_version('Gtk', '4.0') +gi.require_version('Gdk', '4.0') +gi.require_version('Gsk', '4.0') +gi.require_version('Graphene', '1.0') +gi.require_version('Pango', '1.0') +from gi.repository import Gdk, GLib, Gsk, Gtk, Graphene, Pango # noqa: E402 + +# SOF compress mel frame format constants (with DSP data header) +SOF_MAGIC_BYTES = struct.pack(' 3: + del buf[:-3] + return None, None, None, None, None + end = idx + SOF_FRAME_BYTES + if end > len(buf): + del buf[:idx] + return None, None, None, None, None + + frame_number = struct.unpack_from(' 1.0: + sub = 1.0 + if sub != self._sub: + self._sub = sub + self.queue_draw() + + def _build_texture(self): + bins = self._mel_buf.shape[1] + frames = self._mel_buf.shape[0] + norm = (self._mel_buf - self._vmin) / (self._vmax - self._vmin) + np.clip(norm, 0.0, 1.0, out=norm) + idx = (norm * 255.0).astype(np.uint8) # (frames, bins) + # Extend on the right by holding the latest column constant so the + # sub-pixel translate has something to reveal at the right edge. + idx_ext = np.empty((frames + 1, bins), dtype=np.uint8) + idx_ext[:frames] = idx + idx_ext[frames] = idx[-1] + idx_img = idx_ext.T[::-1, :] # (bins, frames+1) + rgba = self._lut[idx_img] # (bins, frames+1, 4) + data = rgba.tobytes() + gbytes = GLib.Bytes.new(data) + return Gdk.MemoryTexture.new( + frames + 1, bins, + Gdk.MemoryFormat.R8G8B8A8, + gbytes, + (frames + 1) * 4, + ) + + def do_snapshot(self, snapshot): + w = self.get_width() + h = self.get_height() + if w <= 0 or h <= 0: + return + frames = self._mel_buf.shape[0] + col_w = w / frames + offset_x = -self._sub * col_w + ext_w = w + col_w + clip_rect = Graphene.Rect().init(0.0, 0.0, float(w), float(h)) + draw_rect = Graphene.Rect().init(offset_x, 0.0, float(ext_w), float(h)) + texture = self._build_texture() + snapshot.push_clip(clip_rect) + if hasattr(snapshot, 'append_scaled_texture'): + snapshot.append_scaled_texture( + texture, Gsk.ScalingFilter.LINEAR, draw_rect) + else: + snapshot.append_texture(texture, draw_rect) + snapshot.pop() + + +class CurveStripArea(Gtk.Widget): + """Strip plot rendered with GSK render nodes (GPU path). + + All drawing goes through native ``Gsk`` nodes so the GL/Vulkan + renderer handles compositing on the GPU: + + * background & grid -> ``snapshot.append_color()`` + * curves -> ``snapshot.append_stroke()`` with ``Gsk.Path`` + * tick / title text -> ``snapshot.append_layout()`` + + Requires GTK 4.14+ (for ``Gsk.PathBuilder`` and + ``Gtk.Snapshot.append_stroke``). + """ + + def __init__(self, get_data, *, ymin=None, ymax=None, autoscale=False, + lines=(), background=(0.10, 0.10, 0.12), + yticks=None, label=None, height_request=80): + super().__init__() + self._get_data = get_data + self._ymin = ymin + self._ymax = ymax + self._autoscale = autoscale + self._lines = lines + self._bg = self._mk_rgba((background[0], background[1], background[2], 1.0)) + self._grid_color = self._mk_rgba((0.4, 0.4, 0.45, 0.6)) + self._text_color = self._mk_rgba((0.85, 0.85, 0.85, 0.9)) + self._line_colors = [self._mk_rgba(rgba) for _, rgba in lines] + self._stroke = Gsk.Stroke.new(1.5) + self._stroke.set_line_cap(Gsk.LineCap.ROUND) + self._stroke.set_line_join(Gsk.LineJoin.ROUND) + self._yticks = yticks + self._label = label + self._tick_font = Pango.FontDescription.from_string("sans-serif 9") + self._title_font = Pango.FontDescription.from_string("sans-serif 10") + self._tick_layouts = None # built lazily once we have a Pango context + self._title_layout = None + self._sub = 0.0 + self.set_hexpand(True) + self.set_size_request(-1, height_request) + + def set_sub_offset(self, sub): + if sub < 0.0: + sub = 0.0 + elif sub > 1.0: + sub = 1.0 + if sub != self._sub: + self._sub = sub + self.queue_draw() + + @staticmethod + def _mk_rgba(t): + c = Gdk.RGBA() + c.red, c.green, c.blue, c.alpha = ( + float(t[0]), float(t[1]), float(t[2]), float(t[3])) + return c + + def _ensure_layouts(self): + if self._tick_layouts is not None: + return + pctx = self.get_pango_context() + self._tick_layouts = [] + if self._yticks: + for yv, txt in self._yticks: + layout = Pango.Layout(pctx) + layout.set_font_description(self._tick_font) + layout.set_text(str(txt), -1) + self._tick_layouts.append((yv, layout)) + if self._label: + self._title_layout = Pango.Layout(pctx) + self._title_layout.set_font_description(self._title_font) + self._title_layout.set_text(self._label, -1) + + def _yrange(self, arrays): + if not self._autoscale: + return self._ymin, self._ymax + vmin = min(float(a.min()) for a in arrays if a.size) + vmax = max(float(a.max()) for a in arrays if a.size) + if vmax - vmin < 1e-6: + vmax = vmin + 1.0 + margin = 0.05 * (vmax - vmin) + return vmin - margin, vmax + margin + + @staticmethod + def _build_path(arr, x_scale, h, ymin, yspan): + """Build a stroke path covering [0, n*x_scale] (one extra sample held).""" + builder = Gsk.PathBuilder.new() + n = arr.shape[0] + ys = h - (arr - ymin) / yspan * h + builder.move_to(0.0, float(ys[0])) + for i in range(1, n): + builder.line_to(float(i * x_scale), float(ys[i])) + # Hold last value for one extra column so the slide-in area is filled. + builder.line_to(float(n * x_scale), float(ys[-1])) + return builder.to_path() + + def do_snapshot(self, snapshot): + w = self.get_width() + h = self.get_height() + if w <= 0 or h <= 0: + return + + bg_rect = Graphene.Rect().init(0.0, 0.0, float(w), float(h)) + snapshot.append_color(self._bg, bg_rect) + + data = self._get_data() + arrays = [data[k] for k, _ in self._lines] + if not arrays or arrays[0].size == 0: + return + n = arrays[0].size + ymin, ymax = self._yrange(arrays) + yspan = (ymax - ymin) or 1.0 + x_scale = w / max(n - 1, 1) + offset_x = -self._sub * x_scale + + # Y grid lines as 1px coloured rectangles (GSK color nodes). + if self._yticks: + for yv, _ in self._yticks: + py = h - (yv - ymin) / yspan * h + grid_rect = Graphene.Rect().init(0.0, py - 0.5, float(w), 1.0) + snapshot.append_color(self._grid_color, grid_rect) + + # Curves -> GSK stroke nodes (GPU rasterised), translated for smooth scroll. + clip_rect = Graphene.Rect().init(0.0, 0.0, float(w), float(h)) + snapshot.push_clip(clip_rect) + snapshot.save() + snapshot.translate(Graphene.Point().init(offset_x, 0.0)) + for (key, _), color in zip(self._lines, self._line_colors): + path = self._build_path(data[key], x_scale, h, ymin, yspan) + snapshot.append_stroke(path, self._stroke, color) + snapshot.restore() + snapshot.pop() + + # Text -> Pango layouts via append_layout (no scroll offset). + self._ensure_layouts() + if self._tick_layouts: + for yv, layout in self._tick_layouts: + py = h - (yv - ymin) / yspan * h + _, ext = layout.get_pixel_extents() + ty = max(0.0, min(float(h) - ext.height, py - ext.height - 1.0)) + snapshot.save() + snapshot.translate(Graphene.Point().init(4.0, ty)) + snapshot.append_layout(layout, self._text_color) + snapshot.restore() + + if self._title_layout is not None: + _, ext = self._title_layout.get_pixel_extents() + snapshot.save() + snapshot.translate( + Graphene.Point().init(float(w) - ext.width - 6.0, 2.0)) + snapshot.append_layout(self._title_layout, self._text_color) + snapshot.restore() + + +class MelViewer: + """Top-level controller: capture process + GTK window.""" + + def __init__(self, app, card, device, width): + self.app = app + self.width_frames = width + + # Ring buffer stored as (frames, mel_bins) for cheap shifts along axis 0. + self.mel_buf = np.zeros((width, SOF_NUM_MEL), dtype=np.float32) + self.vad_buf = np.zeros(width, dtype=np.float32) + self.energy_buf = np.zeros(width, dtype=np.float32) + self.noise_buf = np.zeros(width, dtype=np.float32) + + self.recv_frames = 0 + self.prev_speech = None + self._closing = False + self._last_frame_mono = None # timestamp of most recent ingest + self._frame_period = SOF_HOP_SEC # nominal 10 ms per hop + + self._build_ui() + + # Start capture + self.proc = start_crecord(card, device) + self.frame_q = queue.Queue() + self.reader = threading.Thread(target=self._reader_thread, daemon=True) + self.reader.start() + + # Vsync-aligned redraws via the frame clock — fires once per monitor + # refresh while the widget is mapped. Falls back to a periodic timer + # if add_tick_callback isn't available for some reason. + self._tick_id = 0 + self._timer_id = 0 + if hasattr(self.mel_area, 'add_tick_callback'): + self._tick_id = self.mel_area.add_tick_callback(self._on_tick_clock) + else: + self._timer_id = GLib.timeout_add(GUI_REFRESH_MS, self._on_timer) + + print(f"Spectrogram width: {width} frames ({width * SOF_HOP_SEC:.1f}s)") + if self._tick_id: + print("GUI refresh: vsync-aligned via frame clock, " + "sub-pixel scroll enabled") + else: + print(f"GUI refresh: fallback timer at {GUI_REFRESH_MS} ms " + f"({1000.0 / GUI_REFRESH_MS:.0f} FPS)") + print() + + def _build_ui(self): + self.window = Gtk.ApplicationWindow(application=self.app) + self.window.set_title("SOF Mel Spectrogram (compress PCM) — DSP VAD") + self.window.set_default_size(1100, 600) + self.window.connect('close-request', self._on_close) + + box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2) + box.set_margin_start(4) + box.set_margin_end(4) + box.set_margin_top(4) + box.set_margin_bottom(4) + self.window.set_child(box) + + self.mel_area = MelSpectrogramArea(self.mel_buf, MEL_VMIN, MEL_VMAX) + self.vad_area = CurveStripArea( + lambda: {'vad': self.vad_buf}, + ymin=-0.1, ymax=1.1, + lines=[('vad', (0.2, 0.9, 0.2, 1.0))], + yticks=[(0, 'Silent'), (1, 'Speech')], + label='VAD', + height_request=70, + ) + self.level_area = CurveStripArea( + lambda: {'energy': self.energy_buf, 'noise': self.noise_buf}, + autoscale=True, + lines=[ + ('energy', (1.0, 0.9, 0.2, 1.0)), + ('noise', (0.2, 0.9, 1.0, 1.0)), + ], + label='Level (Y=speech, C=noise)', + height_request=110, + ) + + # Vertical stretch: spectrogram dominates. + self.mel_area.set_vexpand(True) + box.append(self.mel_area) + box.append(self.vad_area) + box.append(self.level_area) + + # --- I/O thread --------------------------------------------------- + def _reader_thread(self): + buf = bytearray() + raw_fd = self.proc.stdout.fileno() + try: + while True: + data = os.read(raw_fd, SOF_FRAME_BYTES * 4) + if not data: + break + buf.extend(data) + while True: + fn, vf, en, no, mi = parse_frame(buf) + if mi is None: + break + self.frame_q.put((fn, vf, en, no, mi)) + except OSError: + pass + self.frame_q.put(None) + + # --- GUI tick ----------------------------------------------------- + def _drain_and_redraw(self): + """Drain queued frames, update sub-pixel offset, queue redraws. + + Returns True while running, False when EOF. + """ + drained = 0 + eof = False + while True: + try: + item = self.frame_q.get_nowait() + except queue.Empty: + break + if item is None: + eof = True + break + fn, vf, en, no, mi = item + self._ingest(fn, vf, en, no, mi) + drained += 1 + if drained >= 64: + break + + # Sub-pixel scroll position: time since last ingest as a fraction of + # the nominal frame period, clamped to [0, 1] so we never overshoot. + if self._last_frame_mono is None: + sub = 0.0 + else: + sub = (time.monotonic() - self._last_frame_mono) / self._frame_period + if sub < 0.0: + sub = 0.0 + elif sub > 1.0: + sub = 1.0 + self.mel_area.set_sub_offset(sub) + self.vad_area.set_sub_offset(sub) + self.level_area.set_sub_offset(sub) + + # If no new data arrived, still queue a redraw so the sub-pixel + # translate advances each vsync. set_sub_offset only does it on + # a change; force it when sub is at 1.0 (no-op) and frames were 0. + if drained == 0 and sub >= 1.0: + # We've slid the maximum distance; nothing to redraw. + pass + + if eof: + self._on_eof() + return False + return True + + def _on_tick_clock(self, _widget, _frame_clock): + return True if self._drain_and_redraw() else False + + def _on_timer(self): + return GLib.SOURCE_CONTINUE if self._drain_and_redraw() \ + else GLib.SOURCE_REMOVE + + def _ingest(self, frame_number, vad_flag, energy, noise, mel_ints): + self.recv_frames += 1 + self._last_frame_mono = time.monotonic() + mel = decode_mel_frame(mel_ints) + speech = vad_flag != 0 + + if speech != self.prev_speech: + t = frame_number * SOF_HOP_SEC + tag = "SPEECH" if speech else "SILENCE" + print(f" [{t:7.2f}s] {tag} (hop {frame_number})", flush=True) + self.prev_speech = speech + + # Scroll left and append new frame on the right. + self.mel_buf[:-1] = self.mel_buf[1:] + self.mel_buf[-1] = mel + self.vad_buf[:-1] = self.vad_buf[1:] + self.vad_buf[-1] = 1.0 if speech else 0.0 + self.energy_buf[:-1] = self.energy_buf[1:] + self.energy_buf[-1] = energy + self.noise_buf[:-1] = self.noise_buf[1:] + self.noise_buf[-1] = noise + + def _on_eof(self): + if self._closing: + return + if self.proc.poll() is None: + self.proc.terminate() + try: + rc = self.proc.wait(timeout=2) + except subprocess.TimeoutExpired: + self.proc.kill() + rc = self.proc.wait() + try: + stderr_out = self.proc.stderr.read().decode(errors='replace') + except Exception: + stderr_out = '' + print(f"\ncrecord exited with code {rc}") + if stderr_out: + print(f"stderr: {stderr_out}") + self.window.close() + + # --- shutdown ----------------------------------------------------- + def _on_close(self, *_args): + self._closing = True + if self._tick_id: + self.mel_area.remove_tick_callback(self._tick_id) + self._tick_id = 0 + if self._timer_id: + GLib.source_remove(self._timer_id) + self._timer_id = 0 + if self.proc.poll() is None: + self.proc.terminate() + try: + self.proc.wait(timeout=3) + except subprocess.TimeoutExpired: + self.proc.kill() + self.proc.wait() + print(f"\nCapture stopped. Received {self.recv_frames} frames.") + return False # allow the window to close + + +def main(): + parser = argparse.ArgumentParser( + description="Live scrolling mel spectrogram viewer " + "from SOF compress PCM capture (GTK 4 / GSK)") + parser.add_argument('--card', '-c', type=int, default=0, + help='ALSA card number (default: 0)') + parser.add_argument('--device', '-d', type=int, default=54, + help='ALSA compress device number (default: 54)') + parser.add_argument('--width', '-w', type=int, default=SPECTROGRAM_WIDTH, + help=f'Spectrogram width in frames (default: {SPECTROGRAM_WIDTH})') + args = parser.parse_args() + + print("=== SOF Mel Spectrogram Viewer (Compress PCM, GTK 4) ===\n") + + # Let Python receive Ctrl-C while the GLib loop is running. + signal.signal(signal.SIGINT, signal.SIG_DFL) + + app = Gtk.Application(application_id='org.sof.mel_spectrogram') + holder = {} + + def on_activate(application): + viewer = MelViewer(application, args.card, args.device, args.width) + viewer.window.present() + holder['viewer'] = viewer + + app.connect('activate', on_activate) + sys.exit(app.run(None)) + + +if __name__ == '__main__': + main() diff --git a/src/audio/mfcc/tune/sof_mel_to_text_live_compress.py b/src/audio/mfcc/tune/sof_mel_to_text_live_compress.py new file mode 100755 index 000000000000..6f0032d241fa --- /dev/null +++ b/src/audio/mfcc/tune/sof_mel_to_text_live_compress.py @@ -0,0 +1,490 @@ +#!/usr/bin/env python3 + +# SPDX-License-Identifier: BSD-3-Clause +# +# Copyright (c) 2026, Intel Corporation. + +"""Live SOF mel capture from compress PCM with DTX-aware Whisper transcription. + +Captures mel frames from ALSA compress device (crecord) with embedded VAD flag. +Frame format: [magic(int32), frame_number(uint32), reserved(int32), + energy(int32), noise_energy(int32), vad_flag(int32), + mel[0..79](int32)] + +With DTX enabled, the DSP sends a configurable number of trailing silence +frames (e.g. 20 = 200ms) after each speech-to-silence transition, then +suppresses further silence. This gives the host enough silence to detect +end-of-speech via a wall-clock patience timer. + +Usage: + python sof_mel_to_text_live_compress.py [--card 0] [--device 54] [--model whisper-medium-int4-ov] +""" + +import argparse +import os +import queue +import struct +import subprocess +import threading +import time +import numpy as np +import openvino as ov +import huggingface_hub as hf_hub +from pathlib import Path + +# SOF compress mel frame format constants (with DSP data header) +SOF_MAGIC_BYTES = struct.pack(' 3: + del buf[:-3] + return None, None, None + end = idx + SOF_FRAME_BYTES + if end > len(buf): + del buf[:idx] + return None, None, None + + # Parse header fields + frame_number = struct.unpack_from('> \"{text}\"\n", flush=True) + else: + print(" [Whisper] empty result", flush=True) + + def flush_speech(t_now): + """Flush speech buffer to Whisper.""" + nonlocal speech_buffer, silence_time, pending_queue, pending_t + if not speech_buffer: + silence_time = None + return + if not try_transcribe(transcriber, speech_buffer, t_now, + on_transcription): + pending_queue = list(speech_buffer) + pending_t = t_now + speech_buffer.clear() + silence_time = None + + try: + while True: + # Calculate queue timeout based on patience timer + get_timeout = 0.1 # default polling interval + if silence_time is not None: + remaining = patience - (time.monotonic() - silence_time) + get_timeout = max(remaining, 0.01) + + try: + item = frame_q.get(timeout=get_timeout) + except queue.Empty: + # Patience expired — flush speech to Whisper + if silence_time is not None: + elapsed = time.monotonic() - silence_time + if elapsed >= patience: + t = last_hop * SOF_HOP_SEC + flush_speech(t) + + # Drain pending queue when Whisper becomes free + if pending_queue is not None and not transcriber.is_busy(): + print(f" [{pending_t:7.2f}s] Whisper free, sending " + f"{len(pending_queue)} queued frames", flush=True) + transcriber.transcribe_async(pending_queue, on_transcription) + pending_queue = None + continue + + if item is None: + # Reader thread ended (crecord exited) + stderr_out = proc.stderr.read().decode(errors='replace') + rc = proc.wait() + print(f"\ncrecord exited with code {rc}") + if stderr_out: + print(f"stderr: {stderr_out}") + break + + frame_number, vad_flag, frame_ints = item + recv_frames += 1 + last_hop = frame_number + mel = decode_mel_frame(frame_ints) + speech = vad_flag != 0 + t = frame_number * SOF_HOP_SEC + + # Print VAD transitions + if speech != prev_speech: + tag = "SPEECH" if speech else "SILENCE" + print(f" [{t:7.2f}s] {tag} (hop {frame_number}, " + f"received {recv_frames})", flush=True) + prev_speech = speech + + # Drain pending queue when Whisper becomes free + if pending_queue is not None and not transcriber.is_busy(): + print(f" [{pending_t:7.2f}s] Whisper free, sending " + f"{len(pending_queue)} queued frames", flush=True) + transcriber.transcribe_async(pending_queue, on_transcription) + pending_queue = None + + # --- Speech buffering logic --- + if speech: + if len(speech_buffer) >= MAX_SPEECH_FRAMES: + n = len(speech_buffer) + duration = n * SOF_HOP_SEC + print(f" [{t:7.2f}s] Buffer full ({duration:.1f}s), " + f"forcing transcription", flush=True) + flush_speech(t) + + speech_buffer.append(mel.copy()) + silence_time = None # speech resumed, cancel patience timer + + else: + # VAD=0: start patience timer if we have buffered speech. + # Don't refresh if already running so trailing silence + # frames don't extend the wait. + if speech_buffer and silence_time is None: + silence_time = time.monotonic() + + except (KeyboardInterrupt, BrokenPipeError): + pass + finally: + # Flush remaining speech + if speech_buffer: + t = last_hop * SOF_HOP_SEC + flush_speech(t) + if proc.poll() is None: + proc.terminate() + try: + proc.wait(timeout=3) + except subprocess.TimeoutExpired: + proc.kill() + proc.wait() + print(f"\n\nCapture stopped. Received {recv_frames} frames.") + + +def main(): + parser = argparse.ArgumentParser( + description="Live SOF mel capture from compress PCM with DTX-aware " + "Whisper transcription") + parser.add_argument('--card', '-c', type=int, default=0, + help='ALSA card number (default: 0)') + parser.add_argument('--device', '-d', type=int, default=54, + help='ALSA compress device number (default: 54)') + parser.add_argument('--model', '-m', default='whisper-medium-int4-ov', + help='Path to Whisper OpenVINO model directory') + parser.add_argument('--encoder-device', default='NPU', + help='OpenVINO device for encoder (default: NPU)') + parser.add_argument('--decoder-device', default='CPU', + help='OpenVINO device for decoder (default: CPU)') + parser.add_argument('--patience', type=float, default=SILENCE_PATIENCE_S, + help=f'Seconds of silence patience before triggering ' + f'transcription (default: {SILENCE_PATIENCE_S})') + args = parser.parse_args() + + model_id = "OpenVINO/" + os.path.basename(args.model) + if not os.path.isdir(args.model): + print(f"Downloading model {model_id} ...") + hf_hub.snapshot_download(model_id, local_dir=args.model) + + print("=== Live SOF Mel → Whisper Transcription (Compress PCM, DTX) ===\n") + run_capture(args.card, args.device, args.model, args.encoder_device, + args.decoder_device, patience=args.patience) + + +if __name__ == '__main__': + main() diff --git a/src/audio/mfcc/tune/sof_mel_to_text_live_dsp_vad.py b/src/audio/mfcc/tune/sof_mel_to_text_live_dsp_vad.py old mode 100644 new mode 100755 index 5c267a57ca79..90941b42822a --- a/src/audio/mfcc/tune/sof_mel_to_text_live_dsp_vad.py +++ b/src/audio/mfcc/tune/sof_mel_to_text_live_dsp_vad.py @@ -1,3 +1,9 @@ +#!/usr/bin/env python3 + +# SPDX-License-Identifier: BSD-3-Clause +# +# Copyright (c) 2026, Intel Corporation. + """Live SOF mel capture with DSP VAD-triggered Whisper transcription. Captures mel frames from ALSA with embedded VAD flag from the DSP. @@ -8,7 +14,6 @@ Usage: python sof_mel_to_text_live_dsp_vad.py [--device hw:0,47] [--model whisper-medium-int4-ov] - python sof_mel_to_text_live_dsp_vad.py --plot # with live spectrogram """ import argparse @@ -22,10 +27,6 @@ import huggingface_hub as hf_hub from pathlib import Path -# Graphics imports deferred until --plot is used -matplotlib = None -plt = None - # SOF mel_s32.raw format constants (with DSP data header) SOF_MAGIC_BYTES = struct.pack('= MAX_SPEECH_FRAMES: @@ -429,11 +359,6 @@ def on_transcription(text): except subprocess.TimeoutExpired: proc.kill() proc.wait() - if plotter is not None: - try: - plt.close(plotter.fig) - except Exception: - pass print("\n\nCapture stopped.") @@ -450,8 +375,6 @@ def main(): help='OpenVINO device for encoder (default: NPU)') parser.add_argument('--decoder-device', default='CPU', help='OpenVINO device for decoder (default: CPU)') - parser.add_argument('--plot', action='store_true', - help='Show live scrolling mel spectrogram and VAD plot') args = parser.parse_args() model_id = "OpenVINO/" + os.path.basename(args.model) if not os.path.isdir(args.model): @@ -460,7 +383,7 @@ def main(): print("=== Live SOF Mel → Whisper Transcription (DSP VAD) ===\n") run_capture(args.device, args.rate, args.model, args.encoder_device, - args.decoder_device, enable_plot=args.plot) + args.decoder_device) if __name__ == '__main__': diff --git a/src/audio/mic_privacy_manager/mic_privacy_manager_intel.c b/src/audio/mic_privacy_manager/mic_privacy_manager_intel.c index 5120c2ab4b59..a5ff86a04fd1 100644 --- a/src/audio/mic_privacy_manager/mic_privacy_manager_intel.c +++ b/src/audio/mic_privacy_manager/mic_privacy_manager_intel.c @@ -10,6 +10,7 @@ #include #include #include +#include const struct device *mic_priv_dev; @@ -88,10 +89,12 @@ void mic_privacy_enable_dmic_irq(bool enable_irq) } } -int mic_privacy_manager_init(void) +__cold int mic_privacy_manager_init(void) { mic_priv_dev = DEVICE_DT_GET(DT_NODELABEL(mic_privacy)); + assert_can_be_cold(); + if (!mic_priv_dev) return -EINVAL; diff --git a/src/audio/mixin_mixout/mixin_mixout.c b/src/audio/mixin_mixout/mixin_mixout.c index 81dfb20e143e..839f0eea8e17 100644 --- a/src/audio/mixin_mixout/mixin_mixout.c +++ b/src/audio/mixin_mixout/mixin_mixout.c @@ -270,7 +270,7 @@ static void mixin_check_notify_underrun(struct comp_dev *dev, struct mixin_data (eos_detected && mixin_data->eos_delay_periods == 0)) { mixin_data->last_reported_underrun = 0; - send_mixer_underrun_notif_msg(dev->ipc_config.id, eos_detected, + send_mixer_underrun_notif_msg(dev->pipeline->pipeline_id, eos_detected, source_avail, sinks_free); } } @@ -412,8 +412,10 @@ static int mixin_process(struct processing_module *mod, frames_to_copy = MIN(source_avail_frames, sinks_free_frames); bytes_to_consume = frames_to_copy * source_get_frame_bytes(sources[0]); - source_get_data(sources[0], bytes_to_consume, (const void **)&source_ptr.ptr, - (const void **)&source_ptr.buf_start, &buf_size); + ret = source_get_data(sources[0], bytes_to_consume, (const void **)&source_ptr.ptr, + (const void **)&source_ptr.buf_start, &buf_size); + if (ret < 0) + return ret; source_ptr.buf_end = (uint8_t *)source_ptr.buf_start + buf_size; } else { /* if source does not produce any data -- do NOT block mixing but generate @@ -458,8 +460,13 @@ static int mixin_process(struct processing_module *mod, size_t free_bytes = sink_get_free_size(sink); size_t buf_size; - sink_get_buffer(sink, free_bytes, &mixout_data->acquired_buf.ptr, - &mixout_data->acquired_buf.buf_start, &buf_size); + ret = sink_get_buffer(sink, free_bytes, &mixout_data->acquired_buf.ptr, + &mixout_data->acquired_buf.buf_start, &buf_size); + /* on failed acquire, skip this mixout and keep mixing the rest; + * source is consumed once after the loop so it is not reprocessed + */ + if (ret < 0) + continue; mixout_data->acquired_buf.buf_end = (uint8_t *)mixout_data->acquired_buf.buf_start + buf_size; mixout_data->acquired_buf_free_frames = @@ -513,7 +520,7 @@ static int mixout_process(struct processing_module *mod, uint32_t frames_to_produce = INT32_MAX; uint32_t bytes_to_produce; struct pending_frames *pending_frames; - int i; + int i, ret; comp_dbg(dev, "entry"); @@ -574,8 +581,11 @@ static int mixout_process(struct processing_module *mod, if (!md->acquired_buf.ptr) { size_t buf_size; - sink_get_buffer(sinks[0], bytes_to_produce, &md->acquired_buf.ptr, - &md->acquired_buf.buf_start, &buf_size); + ret = sink_get_buffer(sinks[0], bytes_to_produce, &md->acquired_buf.ptr, + &md->acquired_buf.buf_start, &buf_size); + if (ret < 0) + return ret; + md->acquired_buf.buf_end = (uint8_t *)md->acquired_buf.buf_start + buf_size; } 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/audio/module_adapter/CMakeLists.txt b/src/audio/module_adapter/CMakeLists.txt index c4b4a43e99e5..ca9ffbd8f787 100644 --- a/src/audio/module_adapter/CMakeLists.txt +++ b/src/audio/module_adapter/CMakeLists.txt @@ -33,6 +33,9 @@ endif() zephyr_library_import(xa_mp3_enc ${CONFIG_CADENCE_CODEC_MP3_ENC_LIB}) endif() + zephyr_library_sources_ifdef(CONFIG_SOF_COMPRESS_CODEC_PCM_DEC + module/compress_other/xa_pcm_dec.c) + if (CONFIG_COMP_DOLBY_DAX_AUDIO_PROCESSING) if(CONFIG_COMP_DOLBY_DAX_AUDIO_PROCESSING STREQUAL "m" AND DEFINED CONFIG_LLEXT) add_subdirectory(module/dolby/llext @@ -148,6 +151,10 @@ if(NOT CONFIG_COMP_MODULE_SHARED_LIBRARY_BUILD) endif() + if(CONFIG_SOF_COMPRESS_CODEC_PCM_DEC) + add_subdirectory(module/compress_other) + endif() + if(CONFIG_COMP_DOLBY_DAX_AUDIO_PROCESSING) target_include_directories(sof PRIVATE ${PROJECT_SOURCE_DIR}/third_party/include) add_local_sources(sof module/dolby/dax.c) diff --git a/src/audio/module_adapter/Kconfig b/src/audio/module_adapter/Kconfig index a9f88dd1f2ad..fe8425ae5467 100644 --- a/src/audio/module_adapter/Kconfig +++ b/src/audio/module_adapter/Kconfig @@ -13,17 +13,6 @@ menu "Processing modules" containers to allocate at once is selected by this config option. - config MODULE_MEMORY_API_DEBUG - bool "Turn on memory API thread safety checks" - default y if DEBUG - help - The Module Memory API structures are not protected - by locks. This is because the initialization, - allocation, and freeing of resources should always - be done in the same thread. This option adds an - assert to make sure no other thread makes such - operations. - config CADENCE_CODEC bool "Cadence codec" help @@ -173,6 +162,14 @@ if CADENCE_CODEC This option is a string and takes the full name of the SRC library binary. endif + config SOF_COMPRESS_CODEC_PCM_DEC + bool "SOF PCM (WAV) decoder" + help + Select to enable the PCM (WAV) passthrough decoder. It plugs + into the Cadence codec wrapper but is an open-source + implementation; no Cadence binary is required. To use it, + build tinycompress cplay with the --enable-pcm option. + endif # Cadence config COMP_DOLBY_DAX_AUDIO_PROCESSING diff --git a/src/audio/module_adapter/module/cadence.c b/src/audio/module_adapter/module/cadence.c index 78a9c68b8afa..672cef86c0a2 100644 --- a/src/audio/module_adapter/module/cadence.c +++ b/src/audio/module_adapter/module/cadence.c @@ -74,6 +74,12 @@ struct cadence_api cadence_api_table[] = { .api = xa_src_pp, }, #endif +#ifdef CONFIG_SOF_COMPRESS_CODEC_PCM_DEC + { + .id = SOF_COMPRESS_CODEC_PCM_DEC_ID, + .api = xa_pcm_dec, + }, +#endif }; static int cadence_codec_get_api_id(uint32_t compress_id, uint32_t direction) @@ -89,6 +95,8 @@ static int cadence_codec_get_api_id(uint32_t compress_id, uint32_t direction) return CADENCE_CODEC_AAC_DEC_ID; case SND_AUDIOCODEC_VORBIS: return CADENCE_CODEC_VORBIS_DEC_ID; + case SND_AUDIOCODEC_PCM: + return SOF_COMPRESS_CODEC_PCM_DEC_ID; default: return -EINVAL; } @@ -221,7 +229,7 @@ size_t cadence_api_table_size(void) return ARRAY_SIZE(cadence_api_table); } -int cadence_codec_get_samples(struct processing_module *mod) +unsigned int cadence_codec_get_samples(struct processing_module *mod) { struct cadence_codec_data *cd = module_get_private_data(mod); struct comp_dev *dev = mod->dev; @@ -236,6 +244,22 @@ int cadence_codec_get_samples(struct processing_module *mod) return 1152; case CADENCE_CODEC_AAC_DEC_ID: return 1024; + case SOF_COMPRESS_CODEC_PCM_DEC_ID: { + /* + * xa_pcm_dec has no fixed frame size: it can produce up to the + * full configured output buffer per call. Report the upper + * bound (ceiling division) so the IPC3 free-space gate covers + * what the codec may actually write even when out_buff_size is + * not an exact multiple of frame_bytes. + */ + struct module_data *codec = &mod->priv; + uint32_t frame_bytes = mod->stream_params->sample_container_bytes * + mod->stream_params->channels; + + if (!frame_bytes) + return 0; + return (codec->mpd.out_buff_size + frame_bytes - 1) / frame_bytes; + } default: break; } @@ -358,6 +382,19 @@ int cadence_codec_apply_params(struct processing_module *mod, int size, void *da */ while (size > 0) { param = data; + + if (size < (int)sizeof(*param)) { + comp_err(dev, "param header truncated, %d bytes left", size); + return -EINVAL; + } + + /* param->size covers the whole record and must fit */ + if (param->size <= sizeof(*param) || param->size > (uint32_t)size) { + comp_err(dev, "invalid param size %u, %d bytes left", + param->size, size); + return -EINVAL; + } + comp_dbg(dev, "cadence_codec_apply_config() applying param %d value %d", param->id, param->data[0]); @@ -573,3 +610,40 @@ int cadence_codec_process_data(struct processing_module *mod, return 0; } + +void cadence_copy_data_from_buffer(void *dest, const void *buffer_ptr, size_t bytes_to_copy, + size_t buffer_size, void const *buffer_start) +{ + size_t bytes_to_end = (size_t)((const uint8_t *)buffer_start + + buffer_size - (const uint8_t *)buffer_ptr); + + if (bytes_to_end >= bytes_to_copy) { + /* No wrap, copy directly */ + memcpy_s(dest, bytes_to_copy, buffer_ptr, bytes_to_copy); + return; + } + + /* Wrap occurs, copy in two parts */ + memcpy_s(dest, bytes_to_end, buffer_ptr, bytes_to_end); + memcpy_s((uint8_t *)dest + bytes_to_end, bytes_to_copy - bytes_to_end, + buffer_start, bytes_to_copy - bytes_to_end); +} + +void cadence_copy_data_to_buffer(void *buffer_ptr, size_t bytes_to_copy, + size_t buffer_size, void *buffer_start, + const void *src) +{ + size_t bytes_to_end = (size_t)((uint8_t *)buffer_start + + buffer_size - (uint8_t *)buffer_ptr); + + if (bytes_to_end >= bytes_to_copy) { + /* No wrap, copy directly */ + memcpy_s(buffer_ptr, bytes_to_copy, src, bytes_to_copy); + return; + } + + /* Wrap occurs, copy in two parts */ + memcpy_s(buffer_ptr, bytes_to_end, src, bytes_to_end); + memcpy_s(buffer_start, bytes_to_copy - bytes_to_end, + (const uint8_t *)src + bytes_to_end, bytes_to_copy - bytes_to_end); +} diff --git a/src/audio/module_adapter/module/cadence_ipc3.c b/src/audio/module_adapter/module/cadence_ipc3.c index d1711e79f800..c9a6f7705062 100644 --- a/src/audio/module_adapter/module/cadence_ipc3.c +++ b/src/audio/module_adapter/module/cadence_ipc3.c @@ -211,71 +211,93 @@ static int cadence_codec_prepare(struct processing_module *mod, return ret; } -static int -cadence_codec_process(struct processing_module *mod, - struct input_stream_buffer *input_buffers, int num_input_buffers, - struct output_stream_buffer *output_buffers, int num_output_buffers) +static int cadence_codec_process(struct processing_module *mod, + struct sof_source **sources, int num_of_sources, + struct sof_sink **sinks, int num_of_sinks) { - struct comp_buffer *local_buff; struct comp_dev *dev = mod->dev; struct module_data *codec = &mod->priv; - int free_bytes, output_bytes = cadence_codec_get_samples(mod) * + size_t output_bytes = cadence_codec_get_samples(mod) * mod->stream_params->sample_container_bytes * mod->stream_params->channels; - uint32_t remaining = input_buffers[0].size; + size_t remaining = source_get_data_available(sources[0]); + const void *source_buffer_start, *src_ptr; + void *sink_ptr, *sink_buffer_start; + size_t src_bytes, sink_bytes; int ret; if (!cadence_codec_deep_buff_allowed(mod)) mod->deep_buff_bytes = 0; /* Proceed only if we have enough data to fill the module buffer completely */ - if (input_buffers[0].size < codec->mpd.in_buff_size) { + if (remaining < codec->mpd.in_buff_size) { comp_dbg(dev, "not enough data to process"); return -ENODATA; } if (!codec->mpd.init_done) { - memcpy_s(codec->mpd.in_buff, codec->mpd.in_buff_size, input_buffers[0].data, - codec->mpd.in_buff_size); + ret = source_get_data(sources[0], codec->mpd.in_buff_size, &src_ptr, + &source_buffer_start, &src_bytes); + if (ret) { + comp_err(dev, "cannot get data from source buffer"); + return ret; + } + + cadence_copy_data_from_buffer(codec->mpd.in_buff, src_ptr, + codec->mpd.in_buff_size, src_bytes, + source_buffer_start); codec->mpd.avail = codec->mpd.in_buff_size; ret = cadence_codec_init_process(mod); - if (ret) + if (ret) { + source_release_data(sources[0], 0); return ret; + } remaining -= codec->mpd.consumed; - input_buffers[0].consumed = codec->mpd.consumed; + source_release_data(sources[0], codec->mpd.consumed); } - /* do not proceed with processing if not enough free space left in the local buffer */ - local_buff = list_first_item(&mod->raw_data_buffers_list, struct comp_buffer, buffers_list); - free_bytes = audio_stream_get_free(&local_buff->stream); - if (free_bytes < output_bytes) + /* do not proceed with processing if not enough free space left in the sink buffer */ + if (sink_get_free_size(sinks[0]) < output_bytes) return -ENOSPC; /* Proceed only if we have enough data to fill the module buffer completely */ if (remaining < codec->mpd.in_buff_size) return -ENODATA; - memcpy_s(codec->mpd.in_buff, codec->mpd.in_buff_size, - (uint8_t *)input_buffers[0].data + input_buffers[0].consumed, - codec->mpd.in_buff_size); + ret = source_get_data(sources[0], codec->mpd.in_buff_size, &src_ptr, &source_buffer_start, + &src_bytes); + if (ret) + return ret; + + cadence_copy_data_from_buffer(codec->mpd.in_buff, src_ptr, + codec->mpd.in_buff_size, src_bytes, + source_buffer_start); codec->mpd.avail = codec->mpd.in_buff_size; comp_dbg(dev, "cadence_codec_process() start"); ret = cadence_codec_process_data(mod, NULL); - if (ret) + if (ret) { + source_release_data(sources[0], 0); return ret; + } + + source_release_data(sources[0], codec->mpd.consumed); + + ret = sink_get_buffer(sinks[0], codec->mpd.produced, &sink_ptr, &sink_buffer_start, + &sink_bytes); + if (ret) { + comp_err(dev, "cannot get sink buffer"); + return ret; + } - /* update consumed with the number of samples consumed during init */ - input_buffers[0].consumed += codec->mpd.consumed; - codec->mpd.consumed = input_buffers[0].consumed; + /* Copy the produced samples into the output buffer */ + cadence_copy_data_to_buffer(sink_ptr, codec->mpd.produced, sink_bytes, + sink_buffer_start, codec->mpd.out_buff); - /* copy the produced samples into the output buffer */ - memcpy_s(output_buffers[0].data, codec->mpd.produced, codec->mpd.out_buff, - codec->mpd.produced); - output_buffers[0].size = codec->mpd.produced; + sink_commit_buffer(sinks[0], codec->mpd.produced); comp_dbg(dev, "cadence_codec_process() done"); @@ -307,7 +329,7 @@ static int cadence_codec_reset(struct processing_module *mod) static const struct module_interface cadence_codec_interface = { .init = cadence_codec_init, .prepare = cadence_codec_prepare, - .process_raw_data = cadence_codec_process, + .process = cadence_codec_process, .set_configuration = cadence_codec_set_configuration, .reset = cadence_codec_reset, .free = cadence_codec_free diff --git a/src/audio/module_adapter/module/cadence_ipc4.c b/src/audio/module_adapter/module/cadence_ipc4.c index 9621d8ab0f1d..763272964d61 100644 --- a/src/audio/module_adapter/module/cadence_ipc4.c +++ b/src/audio/module_adapter/module/cadence_ipc4.c @@ -209,6 +209,9 @@ static int cadence_configure_codec_params(struct processing_module *mod) case CADENCE_CODEC_VORBIS_DEC_ID: /* No configuration needed for Vorbis */ return 0; + case SOF_COMPRESS_CODEC_PCM_DEC_ID: + /* No configuration needed for PCM decoder */ + return 0; default: break; } @@ -217,6 +220,32 @@ static int cadence_configure_codec_params(struct processing_module *mod) return -EINVAL; } +static struct ipc_msg *cadence_codec_notification_init(struct processing_module *mod) +{ + struct comp_dev *dev = mod->dev; + struct comp_ipc_config *ipc_config = &dev->ipc_config; + struct sof_ipc4_notify_module_data *msg_module_data; + union ipc4_notification_header primary; + struct ipc_msg *msg; + + primary.dat = 0; + primary.r.notif_type = SOF_IPC4_MODULE_NOTIFICATION; + primary.r.type = SOF_IPC4_GLB_NOTIFICATION; + primary.r.rsp = SOF_IPC4_MESSAGE_DIR_MSG_REQUEST; + primary.r.msg_tgt = SOF_IPC4_MESSAGE_TARGET_FW_GEN_MSG; + msg = ipc_msg_w_ext_init(primary.dat, 0, sizeof(*msg_module_data)); + if (!msg) + return NULL; + + msg_module_data = (struct sof_ipc4_notify_module_data *)msg->tx_data; + msg_module_data->instance_id = IPC4_INST_ID(ipc_config->id); + msg_module_data->module_id = IPC4_MOD_ID(ipc_config->id); + msg_module_data->event_id = SOF_IPC4_NOTIFY_MODULE_EVENTID_COMPR_MAGIC_VAL; + msg_module_data->event_data_size = 0; + + return msg; +} + static int cadence_codec_init(struct processing_module *mod) { struct module_data *codec = &mod->priv; @@ -237,6 +266,13 @@ static int cadence_codec_init(struct processing_module *mod) } codec->private = cd; + cd->msg = cadence_codec_notification_init(mod); + if (!cd->msg) { + comp_err(dev, "failed to allocate IPC notification template"); + ret = -ENOMEM; + goto free_cd; + } + memcpy_s(&cd->base_cfg, sizeof(cd->base_cfg), &cfg->base_cfg, sizeof(cd->base_cfg)); codec->mpd.init_done = 0; @@ -245,6 +281,16 @@ static int cadence_codec_init(struct processing_module *mod) if (codec->state == MODULE_DISABLED && ext_data->module_data_size > 0) { int size = ext_data->module_data_size; uint8_t *init_bytes; + uint32_t direction; + + /* the init payload holds the codec params followed by the + * direction word; validate the size up front before using it + */ + if (size < (int)(sizeof(struct snd_codec) + sizeof(uint32_t))) { + comp_err(dev, "setup config too small: %d", size); + ret = -EINVAL; + goto free_cd; + } setup_cfg = &cd->setup_cfg; @@ -253,7 +299,7 @@ static int cadence_codec_init(struct processing_module *mod) if (!setup_cfg->data) { comp_err(dev, "failed to alloc setup config"); ret = -ENOMEM; - goto free_cd; + goto free_notification; } setup_cfg->size = size; @@ -265,9 +311,13 @@ static int cadence_codec_init(struct processing_module *mod) setup_cfg->avail = true; codec->cfg.avail = false; - /* direction follows the codec params in init data */ + /* direction follows the codec params; copy it out rather than + * dereferencing a possibly unaligned uint32_t pointer + */ init_bytes = (uint8_t *)ext_data->module_data; - cd->direction = *(uint32_t *)(init_bytes + sizeof(struct snd_codec)); + memcpy(&direction, init_bytes + sizeof(struct snd_codec), + sizeof(direction)); + cd->direction = direction; comp_info(dev, "codec direction set to %u", cd->direction); } @@ -315,6 +365,8 @@ static int cadence_codec_init(struct processing_module *mod) free_cfg: if (setup_cfg) mod_free(mod, setup_cfg->data); +free_notification: + ipc_msg_free(cd->msg); free_cd: mod_free(mod, cd); @@ -395,24 +447,6 @@ static int cadence_codec_prepare(struct processing_module *mod, return 0; } -static void cadence_copy_data_from_buffer(void *dest, const void *buffer_ptr, size_t bytes_to_copy, - size_t buffer_size, uint8_t const *buffer_start) -{ - size_t bytes_to_end = (size_t)((uint8_t *)buffer_start + - buffer_size - (uint8_t *)buffer_ptr); - - if (bytes_to_end >= bytes_to_copy) { - /* No wrap, copy directly */ - memcpy_s(dest, bytes_to_copy, buffer_ptr, bytes_to_copy); - return; - } - - /* Wrap occurs, copy in two parts */ - memcpy_s(dest, bytes_to_end, buffer_ptr, bytes_to_end); - memcpy_s((uint8_t *)dest + bytes_to_end, bytes_to_copy - bytes_to_end, - buffer_start, bytes_to_copy - bytes_to_end); -} - static int cadence_codec_process(struct processing_module *mod, struct sof_source **sources, int num_of_sources, struct sof_sink **sinks, int num_of_sinks) { @@ -420,11 +454,10 @@ static int cadence_codec_process(struct processing_module *mod, struct sof_sourc struct module_data *codec = &mod->priv; size_t in_size = source_get_data_available(sources[0]); size_t out_space = sink_get_free_size(sinks[0]); - uint8_t const *source_buffer_start; - int consumed_during_init = 0; + const void *source_buffer_start, *src_ptr; + void *sink_buffer_start, *sink_ptr; + size_t src_bytes, sink_bytes; uint32_t remaining = in_size; - const void *src_ptr; - size_t src_bytes; int ret; if (!codec->mpd.init_done) { @@ -446,7 +479,6 @@ static int cadence_codec_process(struct processing_module *mod, struct sof_sourc remaining -= codec->mpd.consumed; source_release_data(sources[0], codec->mpd.consumed); - consumed_during_init = codec->mpd.consumed; } codec->mpd.consumed = 0; @@ -456,8 +488,8 @@ static int cadence_codec_process(struct processing_module *mod, struct sof_sourc return -ENODATA; /* Acquire data from the source buffer */ - ret = source_get_data(sources[0], codec->mpd.in_buff_size, &src_ptr, - (const void **)&source_buffer_start, &src_bytes); + ret = source_get_data(sources[0], codec->mpd.in_buff_size, &src_ptr, &source_buffer_start, + &src_bytes); cadence_copy_data_from_buffer(codec->mpd.in_buff, src_ptr, codec->mpd.in_buff_size, src_bytes, source_buffer_start); @@ -472,30 +504,10 @@ static int cadence_codec_process(struct processing_module *mod, struct sof_sourc } if (codec->mpd.eos_reached && !codec->mpd.eos_notification_sent) { - struct ipc_msg msg_proto; - struct comp_ipc_config *ipc_config = &dev->ipc_config; - union ipc4_notification_header *primary = - (union ipc4_notification_header *)&msg_proto.header; - struct sof_ipc4_notify_module_data *msg_module_data; - struct ipc_msg *msg; - - memset_s(&msg_proto, sizeof(msg_proto), 0, sizeof(msg_proto)); - primary->r.notif_type = SOF_IPC4_MODULE_NOTIFICATION; - primary->r.type = SOF_IPC4_GLB_NOTIFICATION; - primary->r.rsp = SOF_IPC4_MESSAGE_DIR_MSG_REQUEST; - primary->r.msg_tgt = SOF_IPC4_MESSAGE_TARGET_FW_GEN_MSG; - msg = ipc_msg_w_ext_init(msg_proto.header, msg_proto.extension, - sizeof(*msg_module_data)); - if (msg) { - msg_module_data = (struct sof_ipc4_notify_module_data *)msg->tx_data; - msg_module_data->instance_id = IPC4_INST_ID(ipc_config->id); - msg_module_data->module_id = IPC4_MOD_ID(ipc_config->id); - msg_module_data->event_id = SOF_IPC4_NOTIFY_MODULE_EVENTID_COMPR_MAGIC_VAL; - msg_module_data->event_data_size = 0; - - ipc_msg_send(msg, NULL, false); - codec->mpd.eos_notification_sent = true; - } + struct cadence_codec_data *cd = module_get_private_data(mod); + + ipc_msg_send(cd->msg, NULL, false); + codec->mpd.eos_notification_sent = true; /* Set EOS for the sink as we are not going to produce more data */ audio_buffer_set_eos(sof_audio_buffer_from_sink(sinks[0])); @@ -507,32 +519,16 @@ static int cadence_codec_process(struct processing_module *mod, struct sof_sourc return -ENOSPC; } - void *sink_ptr; - size_t sink_bytes; - uint8_t const *sink_buffer_start; - - ret = sink_get_buffer(sinks[0], codec->mpd.produced, &sink_ptr, - (void **)&sink_buffer_start, &sink_bytes); + ret = sink_get_buffer(sinks[0], codec->mpd.produced, &sink_ptr, &sink_buffer_start, + &sink_bytes); if (ret) { comp_err(dev, "cannot get sink buffer"); return ret; } /* Copy the produced samples into the output buffer */ - size_t bytes_to_end = (size_t)((uint8_t *)sink_buffer_start + - sink_bytes - (uint8_t *)sink_ptr); - - if (bytes_to_end >= codec->mpd.produced) { - /* No wrap, copy directly */ - memcpy_s(sink_ptr, codec->mpd.produced, codec->mpd.out_buff, - codec->mpd.produced); - } else { - /* Wrap occurs, copy in two parts */ - memcpy_s(sink_ptr, bytes_to_end, codec->mpd.out_buff, bytes_to_end); - memcpy_s((uint8_t *)sink_buffer_start, codec->mpd.produced - bytes_to_end, - (uint8_t *)codec->mpd.out_buff + bytes_to_end, - codec->mpd.produced - bytes_to_end); - } + cadence_copy_data_to_buffer(sink_ptr, codec->mpd.produced, sink_bytes, + sink_buffer_start, codec->mpd.out_buff); source_release_data(sources[0], codec->mpd.consumed); sink_commit_buffer(sinks[0], codec->mpd.produced); @@ -568,13 +564,22 @@ static bool cadence_is_ready_to_process(struct processing_module *mod, return true; } +static int ipc4_cadence_codec_free(struct processing_module *mod) +{ + struct cadence_codec_data *cd = module_get_private_data(mod); + + ipc_msg_free(cd->msg); + + return cadence_codec_free(mod); +} + static const struct module_interface cadence_codec_interface = { .init = cadence_codec_init, .prepare = cadence_codec_prepare, .process = cadence_codec_process, .set_configuration = cadence_codec_set_configuration, .reset = cadence_codec_reset, - .free = cadence_codec_free, + .free = ipc4_cadence_codec_free, .is_ready_to_process = cadence_is_ready_to_process, }; diff --git a/src/audio/module_adapter/module/compress_other/CMakeLists.txt b/src/audio/module_adapter/module/compress_other/CMakeLists.txt new file mode 100644 index 000000000000..4261b9cf8c71 --- /dev/null +++ b/src/audio/module_adapter/module/compress_other/CMakeLists.txt @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: BSD-3-Clause + +if(CONFIG_SOF_COMPRESS_CODEC_PCM_DEC) + add_local_sources(sof xa_pcm_dec.c) +endif() diff --git a/src/audio/module_adapter/module/compress_other/xa_pcm_dec.c b/src/audio/module_adapter/module/compress_other/xa_pcm_dec.c new file mode 100644 index 000000000000..7e31cd768614 --- /dev/null +++ b/src/audio/module_adapter/module/compress_other/xa_pcm_dec.c @@ -0,0 +1,311 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 Intel Corporation. +// + +#include +#include +#include +#include +#include +#include +#include + +/* For XA_API_CMD_GET_MEM_INFO_SIZE */ +#define PCM_DEC_IN_BUF_SIZE 16384 +#define PCM_DEC_OUT_BUF_SIZE 16384 + +/* Maximum library-name buffer size advertised to callers of + * XA_API_CMD_GET_LIB_ID_STRINGS. Must stay <= LIB_NAME_MAX_LEN (30) from + * sof/audio/module_adapter/module/cadence.h, which is the size of the + * destination buffer (cadence_codec_data::name). + */ +#define XA_PCM_DEC_LIB_NAME_MAX 30 + +/* Number of XA_CMD_TYPE_DO_EXECUTE calls after input_over before declaring + * end-of-stream when the host keeps providing full input buffers. PCM + * passthrough has no internal state to detect end-of-data, so this is a + * timeout fallback used when the input buffer never shrinks below the full + * size. With a 16 KB input buffer at 48 kHz stereo S16, each full buffer is + * ~85 ms, so ~12 calls bound the trailing silence to ~1 s. + */ +#define PCM_DEC_EOS_FULL_BUF_COUNT 12 + +/* PCM decoder state structure */ +struct xa_pcm_dec_state { + /* Configuration parameters */ + uint32_t sample_rate; + uint32_t num_channels; + uint32_t pcm_width; + + /* State variables */ + uint32_t bytes_consumed; + uint32_t bytes_produced; + uint32_t init_done; + uint32_t exec_done; + uint32_t input_over; + uint32_t eos_full_buf_count; + + /* Memory pointers */ + void *input_buf; + void *output_buf; + uint32_t output_buf_size; + uint32_t input_bytes; +}; + +static const char lib_name[] = "PCM Decoder"; + +/* Main codec API function */ +XA_ERRORCODE xa_pcm_dec(xa_codec_handle_t handle, WORD32 cmd, WORD32 idx, pVOID value) +{ + struct xa_pcm_dec_state *state = (struct xa_pcm_dec_state *)handle; + + /* Commands that don't need initialized state */ + switch (cmd) { + case XA_API_CMD_GET_API_SIZE: + *(WORD32 *)value = sizeof(struct xa_pcm_dec_state); + return XA_NO_ERROR; + + case XA_API_CMD_GET_LIB_ID_STRINGS: + if (idx == XA_CMD_TYPE_LIB_NAME) { + memcpy_s(value, XA_PCM_DEC_LIB_NAME_MAX, + lib_name, sizeof(lib_name)); + return XA_NO_ERROR; + } + return XA_API_FATAL_INVALID_CMD_TYPE; + + case XA_API_CMD_GET_MEMTABS_SIZE: + /* PCM decoder needs minimal memtabs structure */ + *(WORD32 *)value = 4; + return XA_NO_ERROR; + + case XA_API_CMD_SET_MEMTABS_PTR: + /* PCM decoder doesn't use memtabs, just return success */ + return XA_NO_ERROR; + + default: + break; + } + + /* All other commands need initialized state */ + if (!handle) + return XA_PCMDEC_EXECUTE_FATAL_UNINITIALIZED; + + switch (cmd) { + case XA_API_CMD_INIT: + switch (idx) { + case XA_CMD_TYPE_INIT_API_PRE_CONFIG_PARAMS: + /* Initialize with default values */ + bzero(state, sizeof(*state)); + state->sample_rate = 48000; + state->num_channels = 2; + state->pcm_width = 16; + return XA_NO_ERROR; + + case XA_CMD_TYPE_INIT_API_POST_CONFIG_PARAMS: + /* Nothing to do here for simple PCM decoder */ + return XA_NO_ERROR; + + case XA_CMD_TYPE_INIT_PROCESS: + state->init_done = 1; + return XA_NO_ERROR; + + case XA_CMD_TYPE_INIT_DONE_QUERY: + *(WORD32 *)value = state->init_done; + return XA_NO_ERROR; + + default: + return XA_API_FATAL_INVALID_CMD_TYPE; + } + + case XA_API_CMD_SET_CONFIG_PARAM: + switch (idx) { + case XA_PCM_DEC_CONFIG_PARAM_SAMPLE_RATE: + state->sample_rate = *(WORD32 *)value; + return XA_NO_ERROR; + + case XA_PCM_DEC_CONFIG_PARAM_CHANNELS: + state->num_channels = *(WORD32 *)value; + return XA_NO_ERROR; + + case XA_PCM_DEC_CONFIG_PARAM_PCM_WIDTH: { + WORD32 width = *(WORD32 *)value; + + if (width != 8 && width != 16 && width != 24 && width != 32) + return XA_PCMDEC_CONFIG_NONFATAL_INVALID_PCM_WIDTH; + state->pcm_width = width; + return XA_NO_ERROR; + } + + case XA_PCM_DEC_CONFIG_PARAM_INTERLEAVE: + return XA_NO_ERROR; + + default: + return XA_API_FATAL_INVALID_CMD_TYPE; + } + + case XA_API_CMD_GET_CONFIG_PARAM: + switch (idx) { + case XA_PCM_DEC_CONFIG_PARAM_SAMPLE_RATE: + *(WORD32 *)value = state->sample_rate; + return XA_NO_ERROR; + + case XA_PCM_DEC_CONFIG_PARAM_CHANNELS: + *(WORD32 *)value = state->num_channels; + return XA_NO_ERROR; + + case XA_PCM_DEC_CONFIG_PARAM_PCM_WIDTH: + *(WORD32 *)value = state->pcm_width; + return XA_NO_ERROR; + + case XA_PCM_DEC_CONFIG_PARAM_PRODUCED: + *(WORD32 *)value = state->bytes_produced; + return XA_NO_ERROR; + + default: + return XA_API_FATAL_INVALID_CMD_TYPE; + } + + case XA_API_CMD_GET_N_MEMTABS: + /* We need 2 memory tables: input and output buffers */ + *(WORD32 *)value = 2; + return XA_NO_ERROR; + + case XA_API_CMD_GET_MEM_INFO_TYPE: + if (idx == 0) + *(WORD32 *)value = XA_MEMTYPE_INPUT; + else if (idx == 1) + *(WORD32 *)value = XA_MEMTYPE_OUTPUT; + else + return XA_API_FATAL_INVALID_CMD_TYPE; + return XA_NO_ERROR; + + case XA_API_CMD_GET_MEM_INFO_SIZE: + if (idx == 0) + *(WORD32 *)value = PCM_DEC_IN_BUF_SIZE; + else if (idx == 1) + *(WORD32 *)value = PCM_DEC_OUT_BUF_SIZE; + else + return XA_API_FATAL_INVALID_CMD_TYPE; + return XA_NO_ERROR; + + case XA_API_CMD_GET_MEM_INFO_ALIGNMENT: + *(WORD32 *)value = 4; /* 4-byte alignment */ + return XA_NO_ERROR; + + case XA_API_CMD_SET_MEM_PTR: + if (idx == 0) { + state->input_buf = value; + } else if (idx == 1) { + state->output_buf = value; + state->output_buf_size = PCM_DEC_OUT_BUF_SIZE; + } else { + return XA_API_FATAL_INVALID_CMD_TYPE; + } + return XA_NO_ERROR; + + case XA_API_CMD_SET_INPUT_BYTES: + state->input_bytes = *(WORD32 *)value; + state->bytes_consumed = 0; + if (state->input_bytes > 0) + state->exec_done = 0; + return XA_NO_ERROR; + + case XA_API_CMD_GET_OUTPUT_BYTES: + *(WORD32 *)value = state->bytes_produced; + return XA_NO_ERROR; + + case XA_API_CMD_GET_CURIDX_INPUT_BUF: + *(WORD32 *)value = state->bytes_consumed; + return XA_NO_ERROR; + + case XA_API_CMD_INPUT_OVER: + /* Indicate no more input buffers will be provided */ + state->input_over = 1; + return XA_NO_ERROR; + + case XA_API_CMD_GET_N_TABLES: + /* PCM decoder doesn't use tables */ + *(WORD32 *)value = 0; + return XA_NO_ERROR; + + case XA_API_CMD_GET_TABLE_PTR: + case XA_API_CMD_SET_TABLE_PTR: + case XA_API_CMD_GET_TABLE_INFO_SIZE: + case XA_API_CMD_GET_TABLE_INFO_ALIGNMENT: + case XA_API_CMD_GET_TABLE_INFO_PRIORITY: + /* PCM decoder doesn't use tables, return success */ + return XA_NO_ERROR; + + case XA_API_CMD_GET_MEM_INFO_PLACEMENT: + case XA_API_CMD_GET_MEM_INFO_PRIORITY: + case XA_API_CMD_SET_MEM_INFO_SIZE: + case XA_API_CMD_SET_MEM_PLACEMENT: + /* Return success for optional memory info commands */ + return XA_NO_ERROR; + + case XA_API_CMD_EXECUTE: + if (idx == XA_CMD_TYPE_DO_EXECUTE) { + uint32_t to_copy; + + state->bytes_produced = 0; + state->bytes_consumed = 0; + + /* + * Buffers must have been set via XA_API_CMD_SET_MEM_PTR + * before EXECUTE. Treat a missing pointer as a fatal + * uninitialized error so the caller fails fast instead + * of silently dropping audio. + */ + if (!state->input_buf || !state->output_buf) + return XA_PCMDEC_EXECUTE_FATAL_UNINITIALIZED; + + /* No data and source still live: signal the wrapper to + * skip this round without treating it as an error. + */ + if (state->input_bytes == 0 && !state->input_over) + return XA_PCMDEC_EXECUTE_NONFATAL_INSUFFICIENT_DATA; + + /* Copy PCM data from input to output */ + to_copy = state->input_bytes; + if (to_copy > state->output_buf_size) + to_copy = state->output_buf_size; + + if (to_copy > 0) { + memcpy_s(state->output_buf, state->output_buf_size, + state->input_buf, to_copy); + state->bytes_produced = to_copy; + state->bytes_consumed = to_copy; + } + + /* PCM passthrough has no internal state to flush. Two + * end-of-stream paths: + * 1. Deterministic: a partial input buffer after + * input_over means the source is drained. + * 2. Fallback: if the host keeps delivering full + * buffers (typical when the DMA ring is kept full), + * declare done after a bounded number of calls past + * input_over so playback ends within ~1 s of the + * real end-of-data. + */ + if (state->input_over) { + if (state->input_bytes < PCM_DEC_IN_BUF_SIZE) { + state->exec_done = 1; + } else if (++state->eos_full_buf_count > + PCM_DEC_EOS_FULL_BUF_COUNT) { + state->exec_done = 1; + } + } + + return XA_NO_ERROR; + } else if (idx == XA_CMD_TYPE_DONE_QUERY) { + /* Query if execution is done */ + *(WORD32 *)value = state->exec_done; + return XA_NO_ERROR; + } + return XA_API_FATAL_INVALID_CMD_TYPE; + + default: + return XA_API_FATAL_INVALID_CMD; + } +} diff --git a/src/audio/module_adapter/module/generic.c b/src/audio/module_adapter/module/generic.c index f9531033dadb..ee1b2df92829 100644 --- a/src/audio/module_adapter/module/generic.c +++ b/src/audio/module_adapter/module/generic.c @@ -12,13 +12,13 @@ */ #include +#include #include #include #include #include #include #include -#include #include #if CONFIG_IPC_MAJOR_4 #include @@ -26,16 +26,6 @@ #include #endif -/* The __ZEPHYR__ condition is to keep cmocka tests working */ -#if CONFIG_MODULE_MEMORY_API_DEBUG && defined(__ZEPHYR__) -#define MEM_API_CHECK_THREAD(res) do { \ - if ((res)->rsrc_mngr != k_current_get()) \ - LOG_WRN("mngr %p != cur %p", (res)->rsrc_mngr, k_current_get()); \ -} while (0) -#else -#define MEM_API_CHECK_THREAD(res) -#endif - LOG_MODULE_DECLARE(module_adapter, CONFIG_SOF_LOG_LEVEL); int module_load_config(struct comp_dev *dev, const void *cfg, size_t size) @@ -58,13 +48,15 @@ int module_load_config(struct comp_dev *dev, const void *cfg, size_t size) if (!dst->data) { /* No space for config available yet, allocate now */ - dst->data = rballoc(SOF_MEM_FLAG_USER, size); + dst->data = sof_heap_alloc(sof_sys_user_heap_get(), + SOF_MEM_FLAG_USER | SOF_MEM_FLAG_LARGE_BUFFER, size, 0); } else if (dst->size != size) { /* The size allocated for previous config doesn't match the new one. * Free old container and allocate new one. */ - rfree(dst->data); - dst->data = rballoc(SOF_MEM_FLAG_USER, size); + sof_heap_free(sof_sys_user_heap_get(), dst->data); + dst->data = sof_heap_alloc(sof_sys_user_heap_get(), + SOF_MEM_FLAG_USER | SOF_MEM_FLAG_LARGE_BUFFER, size, 0); } if (!dst->data) { comp_err(dev, "failed to allocate space for setup config."); @@ -87,6 +79,7 @@ void mod_resource_init(struct processing_module *mod) struct module_resources *res = &mod->priv.resources; /* Init memory list */ + k_mutex_init(&res->lock); list_init(&res->objpool.list); res->objpool.heap = res->alloc->heap; res->objpool.vreg = res->alloc->vreg; @@ -121,9 +114,6 @@ int module_init(struct processing_module *mod) return -EIO; } -#if CONFIG_MODULE_MEMORY_API_DEBUG && defined(__ZEPHYR__) - mod->priv.resources.rsrc_mngr = k_current_get(); -#endif /* Now we can proceed with module specific initialization */ #if CONFIG_SOF_USERSPACE_APPLICATION if (mod->dev->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_DP) @@ -184,20 +174,23 @@ void mod_heap_info(struct processing_module *mod, size_t *size, uintptr_t *start * unloaded. The back-end, rballoc(), always aligns the memory to * PLATFORM_DCACHE_ALIGN at the minimum. */ -void *mod_balloc_align(struct processing_module *mod, size_t size, size_t alignment) +void *z_impl_mod_balloc_align(struct processing_module *mod, size_t size, size_t alignment) { struct module_resources *res = &mod->priv.resources; struct module_resource *container; - MEM_API_CHECK_THREAD(res); + k_mutex_lock(&res->lock, K_FOREVER); container = container_get(mod); - if (!container) + if (!container) { + k_mutex_unlock(&res->lock); return NULL; + } if (!size) { comp_err(mod->dev, "requested allocation of 0 bytes."); container_put(mod, container); + k_mutex_unlock(&res->lock); return NULL; } @@ -209,6 +202,7 @@ void *mod_balloc_align(struct processing_module *mod, size_t size, size_t alignm comp_err(mod->dev, "Failed to alloc %zu bytes %zu alignment for comp %#x.", size, alignment, dev_comp_id(mod->dev)); container_put(mod, container); + k_mutex_unlock(&res->lock); return NULL; } /* Store reference to allocated memory */ @@ -220,9 +214,10 @@ void *mod_balloc_align(struct processing_module *mod, size_t size, size_t alignm if (res->heap_usage > res->heap_high_water_mark) res->heap_high_water_mark = res->heap_usage; + k_mutex_unlock(&res->lock); return ptr; } -EXPORT_SYMBOL(mod_balloc_align); +EXPORT_SYMBOL(z_impl_mod_balloc_align); /** * Allocates aligned memory block with flags for module. @@ -240,15 +235,18 @@ void *z_impl_mod_alloc_ext(struct processing_module *mod, uint32_t flags, size_t struct module_resources *res = &mod->priv.resources; struct module_resource *container; - MEM_API_CHECK_THREAD(res); + k_mutex_lock(&res->lock, K_FOREVER); container = container_get(mod); - if (!container) + if (!container) { + k_mutex_unlock(&res->lock); return NULL; + } if (!size) { comp_err(mod->dev, "requested allocation of 0 bytes."); container_put(mod, container); + k_mutex_unlock(&res->lock); return NULL; } @@ -259,6 +257,7 @@ void *z_impl_mod_alloc_ext(struct processing_module *mod, uint32_t flags, size_t comp_err(mod->dev, "Failed to alloc %zu bytes %zu alignment for comp %#x.", size, alignment, dev_comp_id(mod->dev)); container_put(mod, container); + k_mutex_unlock(&res->lock); return NULL; } /* Store reference to allocated memory */ @@ -270,6 +269,7 @@ void *z_impl_mod_alloc_ext(struct processing_module *mod, uint32_t flags, size_t if (res->heap_usage > res->heap_high_water_mark) res->heap_high_water_mark = res->heap_usage; + k_mutex_unlock(&res->lock); return ptr; } EXPORT_SYMBOL(z_impl_mod_alloc_ext); @@ -282,21 +282,24 @@ EXPORT_SYMBOL(z_impl_mod_alloc_ext); * Like comp_data_blob_handler_new() but the handler is automatically freed. */ #if CONFIG_COMP_BLOB -struct comp_data_blob_handler *mod_data_blob_handler_new(struct processing_module *mod) +struct comp_data_blob_handler *z_impl_mod_data_blob_handler_new(struct processing_module *mod) { - struct module_resources * __maybe_unused res = &mod->priv.resources; + struct module_resources *res = &mod->priv.resources; struct comp_data_blob_handler *bhp; struct module_resource *container; - MEM_API_CHECK_THREAD(res); + k_mutex_lock(&res->lock, K_FOREVER); container = container_get(mod); - if (!container) + if (!container) { + k_mutex_unlock(&res->lock); return NULL; + } bhp = comp_data_blob_handler_new_ext(mod->dev, false, NULL, NULL); if (!bhp) { container_put(mod, container); + k_mutex_unlock(&res->lock); return NULL; } @@ -304,9 +307,10 @@ struct comp_data_blob_handler *mod_data_blob_handler_new(struct processing_modul container->size = 0; container->type = MOD_RES_BLOB_HANDLER; + k_mutex_unlock(&res->lock); return bhp; } -EXPORT_SYMBOL(mod_data_blob_handler_new); +EXPORT_SYMBOL(z_impl_mod_data_blob_handler_new); #endif /** @@ -324,15 +328,18 @@ const void *z_impl_mod_fast_get(struct processing_module *mod, const void * cons struct module_resource *container; const void *ptr; - MEM_API_CHECK_THREAD(res); + k_mutex_lock(&res->lock, K_FOREVER); container = container_get(mod); - if (!container) + if (!container) { + k_mutex_unlock(&res->lock); return NULL; + } ptr = fast_get(res->alloc, dram_ptr, size); if (!ptr) { container_put(mod, container); + k_mutex_unlock(&res->lock); return NULL; } @@ -340,6 +347,7 @@ const void *z_impl_mod_fast_get(struct processing_module *mod, const void * cons container->size = 0; container->type = MOD_RES_FAST_GET; + k_mutex_unlock(&res->lock); return ptr; } EXPORT_SYMBOL(z_impl_mod_fast_get); @@ -411,14 +419,17 @@ int z_impl_mod_free(struct processing_module *mod, const void *ptr) { struct module_resources *res = &mod->priv.resources; - MEM_API_CHECK_THREAD(res); if (!ptr) return 0; /* Find which container holds this memory */ struct mod_res_cb_arg cb_arg = {mod, ptr}; + + k_mutex_lock(&res->lock, K_FOREVER); int ret = objpool_iterate(&res->objpool, mod_res_free, &cb_arg); + k_mutex_unlock(&res->lock); + if (ret < 0) comp_err(mod->dev, "error: could not find memory pointed by %p", ptr); @@ -462,6 +473,20 @@ void *z_vrfy_mod_alloc_ext(struct processing_module *mod, uint32_t flags, size_t } #include +void *z_vrfy_mod_balloc_align(struct processing_module *mod, size_t size, size_t alignment) +{ + size_t h_size = 0; + uintptr_t h_start; + + K_OOPS(K_SYSCALL_MEMORY_WRITE(mod, sizeof(*mod))); + mod_heap_info(mod, &h_size, &h_start); + if (h_size) + K_OOPS(K_SYSCALL_MEMORY_WRITE(h_start, h_size)); + + return z_impl_mod_balloc_align(mod, size, alignment); +} +#include + int z_vrfy_mod_free(struct processing_module *mod, const void *ptr) { size_t h_size = 0; @@ -475,6 +500,22 @@ int z_vrfy_mod_free(struct processing_module *mod, const void *ptr) return z_impl_mod_free(mod, ptr); } #include + +#if CONFIG_COMP_BLOB +struct comp_data_blob_handler *z_vrfy_mod_data_blob_handler_new(struct processing_module *mod) +{ + size_t h_size = 0; + uintptr_t h_start; + + K_OOPS(K_SYSCALL_MEMORY_WRITE(mod, sizeof(*mod))); + mod_heap_info(mod, &h_size, &h_start); + if (h_size) + K_OOPS(K_SYSCALL_MEMORY_WRITE(h_start, h_size)); + + return z_impl_mod_data_blob_handler_new(mod); +} +#include +#endif #endif #if CONFIG_COMP_BLOB @@ -539,7 +580,7 @@ int module_prepare(struct processing_module *mod, * as it has been applied during the procedure - it is safe to * free it. */ - rfree(md->cfg.data); + sof_heap_free(sof_sys_user_heap_get(), md->cfg.data); md->cfg.avail = false; md->cfg.data = NULL; @@ -674,7 +715,7 @@ int module_reset(struct processing_module *mod) md->cfg.avail = false; md->cfg.size = 0; - rfree(md->cfg.data); + sof_heap_free(sof_sys_user_heap_get(), md->cfg.data); md->cfg.data = NULL; #if CONFIG_IPC_MAJOR_3 @@ -697,13 +738,13 @@ void mod_free_all(struct processing_module *mod) { struct module_resources *res = &mod->priv.resources; - MEM_API_CHECK_THREAD(res); - /* Free all contents found in used containers */ struct mod_res_cb_arg cb_arg = {mod, NULL}; + k_mutex_lock(&res->lock, K_FOREVER); objpool_iterate(&res->objpool, mod_res_free, &cb_arg); objpool_prune(&res->objpool); + k_mutex_unlock(&res->lock); /* Make sure resource lists and accounting are reset */ mod_resource_init(mod); @@ -725,10 +766,10 @@ int module_free(struct processing_module *mod) /* Free all memory shared by module_adapter & module */ md->cfg.avail = false; md->cfg.size = 0; - rfree(md->cfg.data); + sof_heap_free(sof_sys_user_heap_get(), md->cfg.data); md->cfg.data = NULL; if (md->runtime_params) { - rfree(md->runtime_params); + sof_heap_free(sof_sys_user_heap_get(), md->runtime_params); md->runtime_params = NULL; } #if CONFIG_IPC_MAJOR_3 @@ -795,7 +836,9 @@ int module_set_configuration(struct processing_module *mod, } /* Allocate buffer for new params */ - md->runtime_params = rballoc(SOF_MEM_FLAG_USER, md->new_cfg_size); + md->runtime_params = sof_heap_alloc(sof_sys_user_heap_get(), + SOF_MEM_FLAG_USER | SOF_MEM_FLAG_LARGE_BUFFER, + md->new_cfg_size, 0); if (!md->runtime_params) { comp_err(dev, "space allocation for new params failed"); return -ENOMEM; @@ -836,7 +879,7 @@ int module_set_configuration(struct processing_module *mod, md->new_cfg_size = 0; if (md->runtime_params) - rfree(md->runtime_params); + sof_heap_free(sof_sys_user_heap_get(), md->runtime_params); md->runtime_params = NULL; return ret; diff --git a/src/audio/module_adapter/module/waves/waves.c b/src/audio/module_adapter/module/waves/waves.c index 6616527d2308..aa800b32f992 100644 --- a/src/audio/module_adapter/module/waves/waves.c +++ b/src/audio/module_adapter/module/waves/waves.c @@ -612,6 +612,15 @@ static int waves_effect_apply_config(struct processing_module *mod) for (index = 0; index < cfg->size && (!ret); param_number++) { uint32_t param_data_size; + /* make sure a whole param header remains before reading + * param->size / param->id below + */ + if (index + header_size > cfg->size) { + comp_err(dev, "module_param header at index %u (header %u) exceeds cfg size %zu", + index, header_size, cfg->size); + return -EINVAL; + } + param = (struct module_param *)((char *)cfg->data + index); param_data_size = param->size - sizeof(param->size) - sizeof(param->id); @@ -753,18 +762,25 @@ static int waves_codec_init_process(struct processing_module *mod) return 0; } -static int -waves_codec_process(struct processing_module *mod, - struct input_stream_buffer *input_buffers, int num_input_buffers, - struct output_stream_buffer *output_buffers, int num_output_buffers) +static int waves_codec_process(struct processing_module *mod, + struct sof_source **sources, int num_of_sources, + struct sof_sink **sinks, int num_of_sinks) { int ret; struct comp_dev *dev = mod->dev; struct module_data *codec = &mod->priv; struct waves_codec_data *waves_codec = codec->private; + const void *src_ptr; + const void *src_buf_start; + size_t src_buf_size; + void *snk_ptr; + void *snk_buf_start; + size_t snk_buf_size; + size_t n_bytes = codec->mpd.in_buff_size; + size_t size_to_wrap; /* Proceed only if we have enough data to fill the module buffer completely */ - if (input_buffers[0].size < codec->mpd.in_buff_size) { + if (source_get_data_available(sources[0]) < n_bytes) { comp_dbg(dev, "not enough data to process"); return -ENODATA; } @@ -772,9 +788,20 @@ waves_codec_process(struct processing_module *mod, if (!codec->mpd.init_done) waves_codec_init_process(mod); - memcpy_s(codec->mpd.in_buff, codec->mpd.in_buff_size, - input_buffers[0].data, codec->mpd.in_buff_size); - codec->mpd.avail = codec->mpd.in_buff_size; + ret = source_get_data(sources[0], n_bytes, &src_ptr, &src_buf_start, &src_buf_size); + if (ret) + return ret; + + /* src_buf_size is the total ring buffer size; handle wrap when copying to in_buff */ + size_to_wrap = (const uint8_t *)src_buf_start + src_buf_size - (const uint8_t *)src_ptr; + if (n_bytes <= size_to_wrap) { + memcpy_s(codec->mpd.in_buff, n_bytes, src_ptr, n_bytes); + } else { + memcpy_s(codec->mpd.in_buff, n_bytes, src_ptr, size_to_wrap); + memcpy_s((uint8_t *)codec->mpd.in_buff + size_to_wrap, n_bytes - size_to_wrap, + src_buf_start, n_bytes - size_to_wrap); + } + codec->mpd.avail = n_bytes; comp_dbg(dev, "start"); @@ -806,18 +833,34 @@ waves_codec_process(struct processing_module *mod, status = MaxxEffect_Process(waves_codec->effect, i_streams, o_streams); if (status) { + source_release_data(sources[0], 0); comp_err(dev, "MaxxEffect_Process returned %d", status); ret = -EINVAL; } else { codec->mpd.produced = waves_codec->o_stream.numAvailableSamples * waves_codec->o_format.numChannels * waves_codec->sample_size_in_bytes; codec->mpd.consumed = codec->mpd.produced; - input_buffers[0].consumed = codec->mpd.consumed; - ret = 0; - /* copy the produced samples into the output buffer */ - memcpy_s(output_buffers[0].data, codec->mpd.produced, codec->mpd.out_buff, - codec->mpd.produced); - output_buffers[0].size = codec->mpd.produced; + + source_release_data(sources[0], codec->mpd.consumed); + + ret = sink_get_buffer(sinks[0], codec->mpd.produced, + &snk_ptr, &snk_buf_start, &snk_buf_size); + if (!ret) { + /* snk_buf_size is the total ring buffer size; handle wrap */ + size_to_wrap = (uint8_t *)snk_buf_start + snk_buf_size - + (uint8_t *)snk_ptr; + if (codec->mpd.produced <= size_to_wrap) { + memcpy_s(snk_ptr, codec->mpd.produced, + codec->mpd.out_buff, codec->mpd.produced); + } else { + memcpy_s(snk_ptr, codec->mpd.produced, + codec->mpd.out_buff, size_to_wrap); + memcpy_s(snk_buf_start, codec->mpd.produced - size_to_wrap, + (const uint8_t *)codec->mpd.out_buff + size_to_wrap, + codec->mpd.produced - size_to_wrap); + } + sink_commit_buffer(sinks[0], codec->mpd.produced); + } } if (ret) @@ -899,7 +942,7 @@ waves_codec_set_configuration(struct processing_module *mod, uint32_t config_id, static const struct module_interface waves_interface = { .init = waves_codec_init, .prepare = waves_codec_prepare, - .process_raw_data = waves_codec_process, + .process = waves_codec_process, .set_configuration = waves_codec_set_configuration, .reset = waves_codec_reset, .free = waves_codec_free diff --git a/src/audio/module_adapter/module_adapter.c b/src/audio/module_adapter/module_adapter.c index 01126446860b..53e8c75c42e2 100644 --- a/src/audio/module_adapter/module_adapter.c +++ b/src/audio/module_adapter/module_adapter.c @@ -65,13 +65,7 @@ static struct vregion *module_adapter_dp_heap_new(const struct comp_ipc_config * /* FIXME: the size will be derived from configuration */ const size_t buf_size = 28 * 1024; - /* - * A 1-to-1 replacement of the original heap implementation would be to - * have "lifetime size" equal to 0. But (1) this is invalid for - * vregion_create() and (2) we gradually move objects, that are simple - * to move to the lifetime buffer. Make it 4k for the beginning. - */ - return vregion_create(4096, buf_size - 4096); + return vregion_create(buf_size); } static struct processing_module *module_adapter_mem_alloc(const struct comp_driver *drv, @@ -92,8 +86,8 @@ static struct processing_module *module_adapter_mem_alloc(const struct comp_driv SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT : SOF_MEM_FLAG_USER; size_t heap_size; - if (config->proc_domain == COMP_PROCESSING_DOMAIN_DP && IS_ENABLED(CONFIG_USERSPACE) && - !IS_ENABLED(CONFIG_SOF_USERSPACE_USE_DRIVER_HEAP)) { + if (config->proc_domain == COMP_PROCESSING_DOMAIN_DP && IS_ENABLED(CONFIG_SOF_VREGIONS) && + IS_ENABLED(CONFIG_USERSPACE) && !IS_ENABLED(CONFIG_SOF_USERSPACE_USE_DRIVER_HEAP)) { mod_vreg = module_adapter_dp_heap_new(config, &heap_size); if (!mod_vreg) { comp_cl_err(drv, "Failed to allocate DP module heap / vregion"); @@ -101,7 +95,12 @@ static struct processing_module *module_adapter_mem_alloc(const struct comp_driv } mod_heap = NULL; } else { +#ifdef CONFIG_SOF_USERSPACE_LL + mod_heap = sof_sys_user_heap_get(); + comp_cl_dbg(drv, "using ll user heap for module"); +#else mod_heap = drv->user_heap; +#endif heap_size = 0; mod_vreg = NULL; } @@ -109,16 +108,16 @@ static struct processing_module *module_adapter_mem_alloc(const struct comp_driv if (!mod_vreg) mod = sof_heap_alloc(mod_heap, flags, sizeof(*mod), 0); else if (flags & SOF_MEM_FLAG_COHERENT) - mod = vregion_alloc_coherent(mod_vreg, VREGION_MEM_TYPE_LIFETIME, sizeof(*mod)); + mod = vregion_alloc_coherent(mod_vreg, sizeof(*mod)); else - mod = vregion_alloc(mod_vreg, VREGION_MEM_TYPE_LIFETIME, sizeof(*mod)); + mod = vregion_alloc(mod_vreg, sizeof(*mod)); if (!mod) { comp_cl_err(drv, "failed to allocate memory for module"); goto emod; } - struct mod_alloc_ctx *alloc = rmalloc(flags, sizeof(*alloc)); + struct mod_alloc_ctx *alloc = sof_heap_alloc(mod_heap, flags, sizeof(*alloc), 0); if (!alloc) goto ealloc; @@ -136,7 +135,7 @@ static struct processing_module *module_adapter_mem_alloc(const struct comp_driv * single-core configurations. */ if (mod_vreg) - dev = vregion_alloc_coherent(mod_vreg, VREGION_MEM_TYPE_LIFETIME, sizeof(*dev)); + dev = vregion_alloc_coherent(mod_vreg, sizeof(*dev)); else dev = sof_heap_alloc(mod_heap, SOF_MEM_FLAG_COHERENT, sizeof(*dev), 0); @@ -154,7 +153,7 @@ static struct processing_module *module_adapter_mem_alloc(const struct comp_driv return mod; edev: - rfree(alloc); + sof_heap_free(mod_heap, alloc); ealloc: if (mod_vreg) vregion_free(mod_vreg, mod); @@ -184,11 +183,11 @@ static void module_adapter_mem_free(struct processing_module *mod) vregion_free(mod_vreg, mod->dev); vregion_free(mod_vreg, mod); if (!vregion_put(mod_vreg)) - rfree(alloc); + sof_heap_free(alloc->heap, alloc); } else { sof_heap_free(mod_heap, mod->dev); sof_heap_free(mod_heap, mod); - rfree(alloc); + sof_heap_free(mod_heap, alloc); } } @@ -527,11 +526,13 @@ int module_adapter_prepare(struct comp_dev *dev) /* allocate memory for input buffers */ if (mod->max_sources) { mod->input_buffers = - rzalloc(memory_flags, sizeof(*mod->input_buffers) * mod->max_sources); + sof_heap_alloc(sof_sys_user_heap_get(), memory_flags, + sizeof(*mod->input_buffers) * mod->max_sources, 0); if (!mod->input_buffers) { comp_err(dev, "failed to allocate input buffers"); return -ENOMEM; } + memset(mod->input_buffers, 0, sizeof(*mod->input_buffers) * mod->max_sources); } else { mod->input_buffers = NULL; } @@ -539,12 +540,14 @@ int module_adapter_prepare(struct comp_dev *dev) /* allocate memory for output buffers */ if (mod->max_sinks) { mod->output_buffers = - rzalloc(memory_flags, sizeof(*mod->output_buffers) * mod->max_sinks); + sof_heap_alloc(sof_sys_user_heap_get(), memory_flags, + sizeof(*mod->output_buffers) * mod->max_sinks, 0); if (!mod->output_buffers) { comp_err(dev, "failed to allocate output buffers"); ret = -ENOMEM; goto in_out_free; } + memset(mod->output_buffers, 0, sizeof(*mod->output_buffers) * mod->max_sinks); } else { mod->output_buffers = NULL; } @@ -605,7 +608,8 @@ int module_adapter_prepare(struct comp_dev *dev) size_t size = MAX(mod->deep_buff_bytes, mod->period_bytes); list_for_item(blist, &dev->bsource_list) { - mod->input_buffers[i].data = rballoc(memory_flags, size); + mod->input_buffers[i].data = sof_heap_alloc(sof_sys_user_heap_get(), + memory_flags, size, 0); if (!mod->input_buffers[i].data) { comp_err(mod->dev, "Failed to alloc input buffer data"); ret = -ENOMEM; @@ -617,7 +621,9 @@ int module_adapter_prepare(struct comp_dev *dev) /* allocate memory for output buffer data */ i = 0; list_for_item(blist, &dev->bsink_list) { - mod->output_buffers[i].data = rballoc(memory_flags, md->mpd.out_buff_size); + mod->output_buffers[i].data = sof_heap_alloc(sof_sys_user_heap_get(), + memory_flags, + md->mpd.out_buff_size, 0); if (!mod->output_buffers[i].data) { comp_err(mod->dev, "Failed to alloc output buffer data"); ret = -ENOMEM; @@ -634,7 +640,6 @@ int module_adapter_prepare(struct comp_dev *dev) buff_size, memory_flags, PLATFORM_DCACHE_ALIGN, BUFFER_USAGE_NOT_SHARED); - uint32_t flags; if (!buffer) { comp_err(dev, "failed to allocate local buffer"); @@ -644,9 +649,7 @@ int module_adapter_prepare(struct comp_dev *dev) vregion_get(md->resources.alloc->vreg); - irq_local_disable(flags); list_item_prepend(&buffer->buffers_list, &mod->raw_data_buffers_list); - irq_local_enable(flags); buffer_set_params(buffer, mod->stream_params, BUFFER_UPDATE_FORCE); audio_buffer_reset(&buffer->audio_buffer); @@ -676,26 +679,23 @@ int module_adapter_prepare(struct comp_dev *dev) list_for_item_safe(blist, _blist, &mod->raw_data_buffers_list) { struct comp_buffer *buffer = container_of(blist, struct comp_buffer, buffers_list); - uint32_t flags; - irq_local_disable(flags); list_item_del(&buffer->buffers_list); - irq_local_enable(flags); buffer_free(buffer); } out_data_free: for (i = 0; i < mod->num_of_sinks; i++) - rfree(mod->output_buffers[i].data); + sof_heap_free(sof_sys_user_heap_get(), mod->output_buffers[i].data); in_data_free: for (i = 0; i < mod->num_of_sources; i++) - rfree(mod->input_buffers[i].data); + sof_heap_free(sof_sys_user_heap_get(), mod->input_buffers[i].data); in_out_free: - rfree(mod->output_buffers); + sof_heap_free(sof_sys_user_heap_get(), mod->output_buffers); mod->output_buffers = NULL; - rfree(mod->input_buffers); + sof_heap_free(sof_sys_user_heap_get(), mod->input_buffers); mod->input_buffers = NULL; return ret; } @@ -1407,14 +1407,16 @@ int module_adapter_reset(struct comp_dev *dev) if (IS_PROCESSING_MODE_RAW_DATA(mod)) { for (i = 0; i < mod->num_of_sinks; i++) - rfree((__sparse_force void *)mod->output_buffers[i].data); + sof_heap_free(sof_sys_user_heap_get(), + (__sparse_force void *)mod->output_buffers[i].data); for (i = 0; i < mod->num_of_sources; i++) - rfree((__sparse_force void *)mod->input_buffers[i].data); + sof_heap_free(sof_sys_user_heap_get(), + (__sparse_force void *)mod->input_buffers[i].data); } if (IS_PROCESSING_MODE_RAW_DATA(mod) || IS_PROCESSING_MODE_AUDIO_STREAM(mod)) { - rfree(mod->output_buffers); - rfree(mod->input_buffers); + sof_heap_free(sof_sys_user_heap_get(), mod->output_buffers); + sof_heap_free(sof_sys_user_heap_get(), mod->input_buffers); mod->num_of_sources = 0; mod->num_of_sinks = 0; @@ -1466,11 +1468,7 @@ void module_adapter_free(struct comp_dev *dev) list_for_item_safe(blist, _blist, &mod->raw_data_buffers_list) { struct comp_buffer *buffer = container_of(blist, struct comp_buffer, buffers_list); - uint32_t flags; - - irq_local_disable(flags); list_item_del(&buffer->buffers_list); - irq_local_enable(flags); buffer_free(buffer); } diff --git a/src/audio/module_adapter/module_adapter_ipc3.c b/src/audio/module_adapter/module_adapter_ipc3.c index 74c8f02afa2f..e6098dec0799 100644 --- a/src/audio/module_adapter/module_adapter_ipc3.c +++ b/src/audio/module_adapter/module_adapter_ipc3.c @@ -184,21 +184,25 @@ static int module_adapter_get_set_params(struct comp_dev *dev, struct sof_ipc_ct const struct module_interface *const interface = mod->dev->drv->adapter_ops; enum module_cfg_fragment_position pos; uint32_t data_offset_size; - static uint32_t size; comp_dbg(dev, "num_of_elem %d, elem remain %d msg_index %u", cdata->num_elems, cdata->elems_remaining, cdata->msg_index); - /* set the fragment position, data offset and config data size */ + /* + * The total fragmented-transfer size is kept per instance in + * mod->runtime_params_size; a file-scope static would be shared across + * all module_adapter components and corrupted by interleaved transfers. + */ if (!cdata->msg_index) { - size = cdata->num_elems + cdata->elems_remaining; - data_offset_size = size; + mod->runtime_params_size = cdata->num_elems + cdata->elems_remaining; + data_offset_size = mod->runtime_params_size; if (cdata->elems_remaining) pos = MODULE_CFG_FRAGMENT_FIRST; else pos = MODULE_CFG_FRAGMENT_SINGLE; } else { - data_offset_size = size - (cdata->num_elems + cdata->elems_remaining); + data_offset_size = mod->runtime_params_size - + (cdata->num_elems + cdata->elems_remaining); if (cdata->elems_remaining) pos = MODULE_CFG_FRAGMENT_MIDDLE; else diff --git a/src/audio/module_adapter/module_adapter_ipc4.c b/src/audio/module_adapter/module_adapter_ipc4.c index 74d39559a004..4e8c91be461c 100644 --- a/src/audio/module_adapter/module_adapter_ipc4.c +++ b/src/audio/module_adapter/module_adapter_ipc4.c @@ -81,10 +81,9 @@ int module_ext_init_decode(const struct comp_driver *drv, struct module_ext_init } ext_data->dp_data = dp_data; comp_cl_info(drv, - "init_ext_obj_dp_data domain %u stack %u interim %u lifetime %u shared %u", + "init_ext_obj_dp_data domain %u stack %u heap %u", dp_data->domain_id, dp_data->stack_bytes, - dp_data->interim_heap_bytes, dp_data->lifetime_heap_bytes, - dp_data->shared_bytes); + dp_data->heap_bytes); break; } case IPC4_MOD_INIT_DATA_ID_MODULE_DATA: @@ -156,6 +155,14 @@ int module_adapter_init_data(struct comp_dev *dev, dst->base_cfg = cfg->base_cfg; dst->size = cfgsz; + /* Host-supplied channel count indexes PLATFORM_MAX_CHANNELS arrays. */ + if (dst->base_cfg.audio_fmt.channels_count == 0 || + dst->base_cfg.audio_fmt.channels_count > PLATFORM_MAX_CHANNELS) { + comp_err(dev, "invalid channels count %u", + dst->base_cfg.audio_fmt.channels_count); + return -EINVAL; + } + if (cfgsz >= sizeof(*cfg)) { int n_in = cfg->base_cfg_ext.nb_input_pins; int n_out = cfg->base_cfg_ext.nb_output_pins; @@ -180,11 +187,16 @@ int module_adapter_init_data(struct comp_dev *dev, } } - /* Assume legacy API if module data was not found in ext_init payload */ - if (!config->ipc_extended_init || !dst->ext_data->module_data) { - dst->init_data = cfg; /* legacy API */ + /* Always provide init_data so legacy modules have a valid pointer. + * Extended-init-aware modules (e.g. cadence) read + * ext_data->module_data directly and never dereference init_data. + * Note: cfg points into the IPC mailbox and must not be used + * persistently; it is reset to NULL by module_adapter_reset_data() + * in module_adapter_new_ext() after module_init() returns. + */ + dst->init_data = cfg; + if (!config->ipc_extended_init || !dst->ext_data->module_data) dst->avail = true; - } return 0; } @@ -264,10 +276,16 @@ int module_get_large_config(struct comp_dev *dev, uint32_t param_id, bool first_ else fragment_size = SOF_IPC_MSG_MAX_SIZE; } else { - if (!last_block) + if (!last_block) { fragment_size = SOF_IPC_MSG_MAX_SIZE; - else + } else { + if (*data_offset_size > md->cfg.size) { + comp_err(dev, "invalid data_offset_size %u > cfg size %zu", + *data_offset_size, md->cfg.size); + return -EINVAL; + } fragment_size = md->cfg.size - *data_offset_size; + } } if (interface->get_configuration) diff --git a/src/audio/multiband_drc/multiband_drc.c b/src/audio/multiband_drc/multiband_drc.c index 84a079134ea1..df69d48a9bab 100644 --- a/src/audio/multiband_drc/multiband_drc.c +++ b/src/audio/multiband_drc/multiband_drc.c @@ -218,6 +218,39 @@ static int multiband_drc_setup(struct processing_module *mod, int16_t channels, return multiband_drc_init_coef(mod, channels, rate); } +static int multiband_drc_validator(struct comp_dev *dev, void *new_data, uint32_t new_data_size) +{ + struct sof_multiband_drc_config *config = new_data; + size_t expected; + + if (new_data_size < sizeof(struct sof_multiband_drc_config) || + new_data_size > SOF_MULTIBAND_DRC_MAX_BLOB_SIZE) { + comp_err(dev, "invalid configuration blob, size %u", new_data_size); + return -EINVAL; + } + + if (new_data_size != config->size) { + comp_err(dev, "blob size %u does not match header size %u", + new_data_size, config->size); + return -EINVAL; + } + + if (!config->num_bands || config->num_bands > SOF_MULTIBAND_DRC_MAX_BANDS) { + comp_err(dev, "invalid num_bands %u", config->num_bands); + return -EINVAL; + } + + expected = sizeof(struct sof_multiband_drc_config) + + (size_t)config->num_bands * sizeof(struct sof_drc_params); + if (new_data_size != expected) { + comp_err(dev, "blob size %u does not match expected %zu for %u bands", + new_data_size, expected, config->num_bands); + return -EINVAL; + } + + return 0; +} + /* * End of Multiband DRC setup code. Next the standard component methods. */ @@ -252,6 +285,11 @@ static int multiband_drc_init(struct processing_module *mod) return -ENOMEM; } + /* Reject malformed blobs at IPC time so a bad run-time update cannot + * replace the working configuration. + */ + comp_data_blob_set_validator(cd->model_handler, multiband_drc_validator); + multiband_drc_reset_state(mod, &cd->state); return 0; @@ -342,7 +380,6 @@ static int multiband_drc_prepare(struct processing_module *mod, struct multiband_drc_comp_data *cd = module_get_private_data(mod); struct comp_dev *dev = mod->dev; struct comp_buffer *sourceb; - size_t data_size; int channels; int rate; int ret = 0; @@ -368,8 +405,9 @@ static int multiband_drc_prepare(struct processing_module *mod, /* Initialize DRC */ comp_dbg(dev, "source_format=%d, sink_format=%d", cd->source_format, cd->source_format); - cd->config = comp_get_data_blob(cd->model_handler, &data_size, NULL); - if (cd->config && data_size > 0) { + cd->config = comp_get_data_blob(cd->model_handler, NULL, NULL); + + if (cd->config) { ret = multiband_drc_setup(mod, channels, rate); if (ret < 0) { comp_err(dev, "error: multiband_drc_setup failed."); diff --git a/src/audio/mux/mux.c b/src/audio/mux/mux.c index 965faaf8037e..1476779e7324 100644 --- a/src/audio/mux/mux.c +++ b/src/audio/mux/mux.c @@ -290,9 +290,11 @@ static int demux_trigger(struct processing_module *mod, int cmd) */ if (cmd == COMP_TRIGGER_PRE_START) { struct comp_buffer *b; + struct comp_dev *sink_comp; - comp_dev_for_each_producer(mod->dev, b) { - if (comp_buffer_get_sink_component(b)->pipeline != mod->dev->pipeline) + comp_dev_for_each_consumer(mod->dev, b) { + sink_comp = comp_buffer_get_sink_component(b); + if (sink_comp && sink_comp->pipeline != mod->dev->pipeline) audio_stream_set_overrun(&b->stream, true); } } diff --git a/src/audio/mux/mux_ipc3.c b/src/audio/mux/mux_ipc3.c index fb00030b5814..b8454043e0fd 100644 --- a/src/audio/mux/mux_ipc3.c +++ b/src/audio/mux/mux_ipc3.c @@ -70,8 +70,10 @@ static int mux_set_values(struct processing_module *mod) } if (cd->comp_type == SOF_COMP_MUX) { - if (mux_mix_check(cfg)) + if (mux_mix_check(cfg)) { comp_err(dev, "mux component is not able to mix channels"); + return -EINVAL; + } } for (i = 0; i < cfg->num_streams; i++) { diff --git a/src/audio/mux/mux_ipc4.c b/src/audio/mux/mux_ipc4.c index dc6877059f57..f5ead3a93368 100644 --- a/src/audio/mux/mux_ipc4.c +++ b/src/audio/mux/mux_ipc4.c @@ -36,9 +36,21 @@ static int build_config(struct processing_module *mod, struct mux_data *cfg) { struct comp_dev *dev = mod->dev; struct comp_data *cd = module_get_private_data(mod); + uint32_t base_ch = cfg->base_cfg.audio_fmt.channels_count; + uint32_t ref_ch = cfg->reference_format.channels_count; + uint32_t out_ch = cfg->output_format.channels_count; int mask = 1; int i; + /* base+reference map 1:1 to contiguous output channels: sink width + * must equal their sum and fit the 8-bit output mask + */ + if (base_ch + ref_ch > PLATFORM_MAX_CHANNELS || out_ch != base_ch + ref_ch) { + comp_err(dev, "invalid channel count: base %u + reference %u -> output %u (max %d)", + base_ch, ref_ch, out_ch, PLATFORM_MAX_CHANNELS); + return -EINVAL; + } + cd->config.num_streams = MUX_MAX_STREAMS; /* clear masks */ @@ -46,12 +58,12 @@ static int build_config(struct processing_module *mod, struct mux_data *cfg) memset(cd->config.streams[i].mask, 0, sizeof(cd->config.streams[i].mask)); /* Setting masks for streams */ - for (i = 0; i < cfg->base_cfg.audio_fmt.channels_count; i++) { + for (i = 0; i < base_ch; i++) { cd->config.streams[0].mask[i] = mask; mask <<= 1; } - for (i = 0; i < cfg->reference_format.channels_count; i++) { + for (i = 0; i < ref_ch; i++) { cd->config.streams[1].mask[i] = mask; mask <<= 1; } @@ -68,7 +80,7 @@ static int build_config(struct processing_module *mod, struct mux_data *cfg) * set up param then verify param. BTW for IPC3 path, the param is sent by * host driver. */ -static void set_mux_params(struct processing_module *mod, struct mux_data *cfg) +static int set_mux_params(struct processing_module *mod, struct mux_data *cfg) { struct sof_ipc_stream_params *params = mod->stream_params; struct comp_data *cd = module_get_private_data(mod); @@ -107,6 +119,12 @@ static void set_mux_params(struct processing_module *mod, struct mux_data *cfg) comp_dev_for_each_producer(dev, source) { j = IPC4_SINK_QUEUE_ID(buf_get_id(source)); + /* host-supplied queue ID indexes streams[] */ + if (j >= MUX_MAX_STREAMS) { + comp_err(dev, "invalid source queue ID %d (valid range 0..%d)", + j, MUX_MAX_STREAMS - 1); + return -EINVAL; + } cd->config.streams[j].pipeline_id = buffer_pipeline_id(source); if (j == BASE_CFG_QUEUED_ID) audio_fmt = &cfg->base_cfg.audio_fmt; @@ -118,6 +136,8 @@ static void set_mux_params(struct processing_module *mod, struct mux_data *cfg) } mux_prepare_look_up_table(mod); + + return 0; } int mux_params(struct processing_module *mod) @@ -137,8 +157,6 @@ int mux_params(struct processing_module *mod) if (ret < 0) return ret; - set_mux_params(mod, cfg); - - return ret; + return set_mux_params(mod, cfg); } #endif /* CONFIG_COMP_MUX */ diff --git a/src/audio/pcm_converter/pcm_remap.c b/src/audio/pcm_converter/pcm_remap.c index 9204b21ee8ab..f0ad6912a5f8 100644 --- a/src/audio/pcm_converter/pcm_remap.c +++ b/src/audio/pcm_converter/pcm_remap.c @@ -68,13 +68,14 @@ static int remap_c16(const struct audio_stream *source, uint32_t dummy1, src_channel = chmap & 0xf; chmap >>= 4; - if (src_channel == 0xf) { + /* 0xf means "mute"; also mute any out-of-range source channel so + * a crafted chmap nibble cannot index past the source frame. + */ + if (src_channel == 0xf || src_channel >= num_src_channels) { mute_channel_c16(sink, sink_channel, frames); continue; } - assert(src_channel < num_src_channels); - src = (int16_t *)audio_stream_get_rptr(source) + src_channel; dst = (int16_t *)audio_stream_get_wptr(sink) + sink_channel; @@ -126,13 +127,14 @@ static inline int remap_c32_left_shift(const struct audio_stream *source, src_channel = chmap & 0xf; chmap >>= 4; - if (src_channel == 0xf) { + /* 0xf means "mute"; also mute any out-of-range source channel so + * a crafted chmap nibble cannot index past the source frame. + */ + if (src_channel == 0xf || src_channel >= num_src_channels) { mute_channel_c32(sink, sink_channel, frames); continue; } - assert(src_channel < num_src_channels); - src = (int32_t *)audio_stream_get_rptr(source) + src_channel; dst = (int32_t *)audio_stream_get_wptr(sink) + sink_channel; @@ -184,13 +186,14 @@ static inline int remap_c32_right_shift(const struct audio_stream *source, src_channel = chmap & 0xf; chmap >>= 4; - if (src_channel == 0xf) { + /* 0xf means "mute"; also mute any out-of-range source channel so + * a crafted chmap nibble cannot index past the source frame. + */ + if (src_channel == 0xf || src_channel >= num_src_channels) { mute_channel_c32(sink, sink_channel, frames); continue; } - assert(src_channel < num_src_channels); - src = (int32_t *)audio_stream_get_rptr(source) + src_channel; dst = (int32_t *)audio_stream_get_wptr(sink) + sink_channel; @@ -243,13 +246,14 @@ static inline int remap_c16_to_c32(const struct audio_stream *source, src_channel = chmap & 0xf; chmap >>= 4; - if (src_channel == 0xf) { + /* 0xf means "mute"; also mute any out-of-range source channel so + * a crafted chmap nibble cannot index past the source frame. + */ + if (src_channel == 0xf || src_channel >= num_src_channels) { mute_channel_c32(sink, sink_channel, frames); continue; } - assert(src_channel < num_src_channels); - src = (int16_t *)audio_stream_get_rptr(source) + src_channel; dst = (int32_t *)audio_stream_get_wptr(sink) + sink_channel; @@ -302,13 +306,14 @@ static inline int remap_c32_to_c16(const struct audio_stream *source, src_channel = chmap & 0xf; chmap >>= 4; - if (src_channel == 0xf) { + /* 0xf means "mute"; also mute any out-of-range source channel so + * a crafted chmap nibble cannot index past the source frame. + */ + if (src_channel == 0xf || src_channel >= num_src_channels) { mute_channel_c16(sink, sink_channel, frames); continue; } - assert(src_channel < num_src_channels); - src = (int32_t *)audio_stream_get_rptr(source) + src_channel; dst = (int16_t *)audio_stream_get_wptr(sink) + sink_channel; diff --git a/src/audio/phase_vocoder/CMakeLists.txt b/src/audio/phase_vocoder/CMakeLists.txt new file mode 100644 index 000000000000..b632c50af6cb --- /dev/null +++ b/src/audio/phase_vocoder/CMakeLists.txt @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: BSD-3-Clause + +if(CONFIG_COMP_PHASE_VOCODER STREQUAL "m" AND DEFINED CONFIG_LLEXT) + add_subdirectory(llext ${PROJECT_BINARY_DIR}/phase_vocoder_llext) + add_dependencies(app phase_vocoder) +else() + add_local_sources(sof phase_vocoder.c) + add_local_sources(sof phase_vocoder_setup.c) + add_local_sources(sof phase_vocoder_common.c) + add_local_sources(sof phase_vocoder-generic.c) + + if(CONFIG_IPC_MAJOR_4) + add_local_sources(sof phase_vocoder-ipc4.c) + endif() +endif() diff --git a/src/audio/phase_vocoder/Kconfig b/src/audio/phase_vocoder/Kconfig new file mode 100644 index 000000000000..a3eecfc8023f --- /dev/null +++ b/src/audio/phase_vocoder/Kconfig @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: BSD-3-Clause + +config COMP_PHASE_VOCODER + tristate "Phase Vocoder component" + default m if LIBRARY_DEFAULT_MODULAR + select MATH_FFT + select MATH_32BIT_FFT + help + Select for phase_vocoder component. The component provides + render speed control in range 0.5-2.0x. The pitch is + preserved in audio waveform stretch or shorten. The module + is using a frequency domain algorithm in STFT domain to + interpolate magnitude and phase of output IFFT frames from + input FFT frames. diff --git a/src/audio/phase_vocoder/llext/CMakeLists.txt b/src/audio/phase_vocoder/llext/CMakeLists.txt new file mode 100644 index 000000000000..ffefa29c7080 --- /dev/null +++ b/src/audio/phase_vocoder/llext/CMakeLists.txt @@ -0,0 +1,11 @@ +# Copyright (c) 2026 Intel Corporation. +# SPDX-License-Identifier: Apache-2.0 + +sof_llext_build("phase_vocoder" + SOURCES ../phase_vocoder.c + ../phase_vocoder_setup.c + ../phase_vocoder_common.c + ../phase_vocoder-generic.c + ../phase_vocoder-ipc4.c + LIB openmodules +) diff --git a/src/audio/phase_vocoder/llext/llext.toml.h b/src/audio/phase_vocoder/llext/llext.toml.h new file mode 100644 index 000000000000..94cfd0d67d50 --- /dev/null +++ b/src/audio/phase_vocoder/llext/llext.toml.h @@ -0,0 +1,6 @@ +#include +#define LOAD_TYPE "2" +#include "../phase_vocoder.toml" + +[module] +count = __COUNTER__ diff --git a/src/audio/phase_vocoder/phase_vocoder-generic.c b/src/audio/phase_vocoder/phase_vocoder-generic.c new file mode 100644 index 000000000000..5cc382cb5b8b --- /dev/null +++ b/src/audio/phase_vocoder/phase_vocoder-generic.c @@ -0,0 +1,694 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 Intel Corporation. + +#include +#include +#include +#include +#include +#include +#include +#include "phase_vocoder.h" + +#if CONFIG_FORMAT_S32LE +/** + * phase_vocoder_source_s32() - Read S32_LE samples into the input ring buffer. + * @cd: Pointer to component private data. + * @source: Source for PCM samples data. + * @frames: Number of audio data frames to consume. + * + * Copies up to @frames frames of S32_LE PCM from @source into the per-channel + * input ring buffers. In mono-mix mode the channels are pre-summed using + * @cd->mono_mix_coef so that downstream STFT processing runs on a single + * channel. + * + * Return: Value zero for success, otherwise an error code. + */ +int phase_vocoder_source_s32(struct phase_vocoder_comp_data *cd, struct sof_source *source, + int frames) +{ + struct phase_vocoder_state *state = &cd->state; + struct phase_vocoder_buffer *ibuf; + int32_t const *x, *x_start, *x_end; + const int32_t mix_gain = cd->mono_mix_coef; + int64_t mix; + int frames_left; + int x_size; + int bytes; + int ret; + int n1; + int n2; + int n; + int i; + int j; + int stream_channels = cd->stream_channels; + int process_channels = cd->process_channels; + bool process_mono = cd->config->mono; + + ibuf = &state->ibuf[0]; + frames = MIN(frames, ibuf->s_free); + bytes = frames * cd->frame_bytes; + + /* Get pointer to source data in circular buffer */ + ret = source_get_data_s32(source, bytes, &x, &x_start, &x_size); + if (ret) + return ret; + + /* Set helper pointers to buffer end for wrap check. Then loop until all + * samples are processed. + */ + x_end = x_start + x_size; + + frames_left = frames; + while (frames_left) { + /* Find out samples to process before first wrap or end of data. */ + ibuf = &state->ibuf[0]; + n1 = (x_end - x) / stream_channels; + n2 = phase_vocoder_buffer_samples_without_wrap(ibuf, ibuf->w_ptr); + n = MIN(n1, n2); + n = MIN(n, frames_left); + if (process_mono) { + for (i = 0; i < n; i++) { + mix = 0; + for (j = 0; j < stream_channels; j++) + mix += Q_MULTSR_32X32((int64_t)mix_gain, *x++, 31, 31, 31); + + *ibuf->w_ptr++ = sat_int32(mix); + } + } else { + for (i = 0; i < n; i++) { + for (j = 0; j < stream_channels; j++) { + ibuf = &state->ibuf[j]; + *ibuf->w_ptr++ = *x++; + } + } + } + + /* One of the buffers needs a wrap (or end of data), so check for wrap */ + for (j = 0; j < process_channels; j++) { + ibuf = &state->ibuf[j]; + ibuf->w_ptr = phase_vocoder_buffer_wrap(ibuf, ibuf->w_ptr); + } + + if (x >= x_end) + x -= x_size; + + /* Update processed samples count for next loop iteration. */ + frames_left -= n; + } + + /* Update the source for bytes consumed. Return success. */ + source_release_data(source, bytes); + for (j = 0; j < process_channels; j++) { + ibuf = &state->ibuf[j]; + ibuf->s_avail += frames; + ibuf->s_free -= frames; + } + + return 0; +} + +/** + * phase_vocoder_sink_s32() - Write processed samples out as S32_LE. + * @cd: Pointer to component private data. + * @sink: Sink for PCM samples data. + * @frames: Number of audio data frames to produce. + * + * Copies up to @frames frames from the per-channel output ring buffer to + * @sink in S32_LE format. In mono-mix mode the single processed channel is + * replicated to all output channels. The output ring buffer slots are + * cleared after read so the next overlap-add pass starts from zero. + * + * Return: Value zero for success, otherwise an error code. + */ +int phase_vocoder_sink_s32(struct phase_vocoder_comp_data *cd, struct sof_sink *sink, int frames) +{ + struct phase_vocoder_state *state = &cd->state; + struct phase_vocoder_buffer *obuf; + int32_t *y, *y_start, *y_end; + int frames_remain; + int bytes; + int y_size; + int ret; + int ch, n1, n, i; + int stream_channels = cd->stream_channels; + int process_channels = cd->process_channels; + bool process_mono = cd->config->mono; + + obuf = &state->obuf[0]; + frames = MIN(frames, obuf->s_avail); + if (!frames) + return 0; + + /* Get pointer to sink data in circular buffer */ + bytes = frames * cd->frame_bytes; + ret = sink_get_buffer_s32(sink, bytes, &y, &y_start, &y_size); + if (ret) + return ret; + + /* Set helper pointers to buffer end for wrap check. Then loop until all + * samples are processed. + */ + y_end = y_start + y_size; + + frames_remain = frames; + while (frames_remain) { + /* Find out samples to process before first wrap or end of data. */ + obuf = &state->obuf[0]; + n1 = (y_end - y) / stream_channels; + n = phase_vocoder_buffer_samples_without_wrap(obuf, obuf->r_ptr); + n = MIN(n1, n); + n = MIN(n, frames_remain); + + if (process_mono) { + for (i = 0; i < n; i++) { + for (ch = 0; ch < stream_channels; ch++) + *y++ = *obuf->r_ptr; + + *obuf->r_ptr++ = 0; /* clear overlap add mix */ + } + } else { + for (i = 0; i < n; i++) { + for (ch = 0; ch < stream_channels; ch++) { + obuf = &state->obuf[ch]; + *y++ = *obuf->r_ptr; + *obuf->r_ptr++ = 0; /* clear overlap add mix */ + } + } + } + + /* One of the buffers needs a wrap (or end of data), so check for wrap */ + for (ch = 0; ch < process_channels; ch++) { + obuf = &state->obuf[ch]; + obuf->r_ptr = phase_vocoder_buffer_wrap(obuf, obuf->r_ptr); + } + + if (y >= y_end) + y -= y_size; + + /* Update processed samples count for next loop iteration. */ + frames_remain -= n; + } + + /* Update the sink for bytes produced. Return success. */ + sink_commit_buffer(sink, bytes); + for (ch = 0; ch < process_channels; ch++) { + obuf = &state->obuf[ch]; + obuf->s_avail -= frames; + obuf->s_free += frames; + } + + return 0; +} +#endif /* CONFIG_FORMAT_S32LE */ + +#if CONFIG_FORMAT_S24LE +/** + * phase_vocoder_source_s24() - Read S24_4LE samples into the input ring buffer. + * @cd: Pointer to component private data. + * @source: Source for PCM samples data. + * @frames: Number of audio data frames to consume. + * + * Copies up to @frames frames of S24_4LE PCM from @source into the per-channel + * input ring buffers, left-shifting each 24-bit sample by 8 to align to Q1.31 + * for STFT processing. In mono-mix mode the channels are pre-summed using + * @cd->mono_mix_coef so that downstream STFT processing runs on a single + * channel. + * + * Return: Value zero for success, otherwise an error code. + */ +int phase_vocoder_source_s24(struct phase_vocoder_comp_data *cd, struct sof_source *source, + int frames) +{ + struct phase_vocoder_state *state = &cd->state; + struct phase_vocoder_buffer *ibuf; + int32_t const *x, *x_start, *x_end; + const int32_t mix_gain = cd->mono_mix_coef; + int64_t mix; + int32_t tmp; + int frames_left; + int x_size; + int bytes; + int ret; + int n1; + int n2; + int n; + int i; + int j; + int stream_channels = cd->stream_channels; + int process_channels = cd->process_channels; + bool process_mono = cd->config->mono; + + ibuf = &state->ibuf[0]; + frames = MIN(frames, ibuf->s_free); + bytes = frames * cd->frame_bytes; + + /* Get pointer to source data in circular buffer */ + ret = source_get_data_s32(source, bytes, &x, &x_start, &x_size); + if (ret) + return ret; + + /* Set helper pointers to buffer end for wrap check. Then loop until all + * samples are processed. + */ + x_end = x_start + x_size; + + frames_left = frames; + while (frames_left) { + /* Find out samples to process before first wrap or end of data. */ + ibuf = &state->ibuf[0]; + n1 = (x_end - x) / stream_channels; + n2 = phase_vocoder_buffer_samples_without_wrap(ibuf, ibuf->w_ptr); + n = MIN(n1, n2); + n = MIN(n, frames_left); + if (process_mono) { + for (i = 0; i < n; i++) { + mix = 0; + for (j = 0; j < stream_channels; j++) { + tmp = (int32_t)(((uint32_t)*x++) << 8); + mix += Q_MULTSR_32X32((int64_t)mix_gain, tmp, 31, 31, 31); + } + + *ibuf->w_ptr++ = sat_int32(mix); + } + } else { + for (i = 0; i < n; i++) { + for (j = 0; j < stream_channels; j++) { + ibuf = &state->ibuf[j]; + tmp = (int32_t)(((uint32_t)*x++) << 8); + *ibuf->w_ptr++ = tmp; + } + } + } + + /* One of the buffers needs a wrap (or end of data), so check for wrap */ + for (j = 0; j < process_channels; j++) { + ibuf = &state->ibuf[j]; + ibuf->w_ptr = phase_vocoder_buffer_wrap(ibuf, ibuf->w_ptr); + } + + if (x >= x_end) + x -= x_size; + + /* Update processed samples count for next loop iteration. */ + frames_left -= n; + } + + /* Update the source for bytes consumed. Return success. */ + source_release_data(source, bytes); + for (j = 0; j < process_channels; j++) { + ibuf = &state->ibuf[j]; + ibuf->s_avail += frames; + ibuf->s_free -= frames; + } + + return 0; +} + +/** + * phase_vocoder_sink_s24() - Write processed samples out as S24_4LE. + * @cd: Pointer to component private data. + * @sink: Sink for PCM samples data. + * @frames: Number of audio data frames to produce. + * + * Copies up to @frames frames from the per-channel output ring buffer to + * @sink in S24_4LE format, right-shifting each Q1.31 sample by 8 to produce + * the 24-bit output. In mono-mix mode the single processed channel is + * replicated to all output channels. The output ring buffer slots are + * cleared after read so the next overlap-add pass starts from zero. + * + * Return: Value zero for success, otherwise an error code. + */ +int phase_vocoder_sink_s24(struct phase_vocoder_comp_data *cd, struct sof_sink *sink, int frames) +{ + struct phase_vocoder_state *state = &cd->state; + struct phase_vocoder_buffer *obuf; + int32_t *y, *y_start, *y_end; + int frames_remain; + int bytes; + int y_size; + int ret; + int ch, n1, n, i; + int stream_channels = cd->stream_channels; + int process_channels = cd->process_channels; + bool process_mono = cd->config->mono; + + obuf = &state->obuf[0]; + frames = MIN(frames, obuf->s_avail); + if (!frames) + return 0; + + /* Get pointer to sink data in circular buffer */ + bytes = frames * cd->frame_bytes; + ret = sink_get_buffer_s32(sink, bytes, &y, &y_start, &y_size); + if (ret) + return ret; + + /* Set helper pointers to buffer end for wrap check. Then loop until all + * samples are processed. + */ + y_end = y_start + y_size; + + frames_remain = frames; + while (frames_remain) { + /* Find out samples to process before first wrap or end of data. */ + obuf = &state->obuf[0]; + n1 = (y_end - y) / stream_channels; + n = phase_vocoder_buffer_samples_without_wrap(obuf, obuf->r_ptr); + n = MIN(n1, n); + n = MIN(n, frames_remain); + + if (process_mono) { + for (i = 0; i < n; i++) { + for (ch = 0; ch < stream_channels; ch++) + *y++ = *obuf->r_ptr >> 8; + + *obuf->r_ptr++ = 0; /* clear overlap add mix */ + } + } else { + for (i = 0; i < n; i++) { + for (ch = 0; ch < stream_channels; ch++) { + obuf = &state->obuf[ch]; + *y++ = *obuf->r_ptr >> 8; + *obuf->r_ptr++ = 0; /* clear overlap add mix */ + } + } + } + + /* One of the buffers needs a wrap (or end of data), so check for wrap */ + for (ch = 0; ch < process_channels; ch++) { + obuf = &state->obuf[ch]; + obuf->r_ptr = phase_vocoder_buffer_wrap(obuf, obuf->r_ptr); + } + + if (y >= y_end) + y -= y_size; + + /* Update processed samples count for next loop iteration. */ + frames_remain -= n; + } + + /* Update the sink for bytes produced. Return success. */ + sink_commit_buffer(sink, bytes); + for (ch = 0; ch < process_channels; ch++) { + obuf = &state->obuf[ch]; + obuf->s_avail -= frames; + obuf->s_free += frames; + } + + return 0; +} +#endif /* CONFIG_FORMAT_S24LE */ + +#if CONFIG_FORMAT_S16LE +/** + * phase_vocoder_source_s16() - Read S16_LE samples into the input ring buffer. + * @cd: Pointer to component private data. + * @source: Source for PCM samples data. + * @frames: Number of audio data frames to consume. + * + * Copies up to @frames frames of S16_LE PCM from @source into the per-channel + * input ring buffers, sign-extending each sample to S32 left-aligned. In + * mono-mix mode the channels are pre-summed using @cd->mono_mix_coef. + * + * Return: Value zero for success, otherwise an error code. + */ +int phase_vocoder_source_s16(struct phase_vocoder_comp_data *cd, struct sof_source *source, + int frames) +{ + struct phase_vocoder_state *state = &cd->state; + struct phase_vocoder_buffer *ibuf; + const int32_t mix_gain = cd->mono_mix_coef; + int64_t mix; + int16_t const *x, *x_start, *x_end; + int16_t in; + int frames_left; + int x_size; + int bytes; + int ret; + int n1; + int n2; + int n; + int i; + int j; + int stream_channels = cd->stream_channels; + int process_channels = cd->process_channels; + bool process_mono = cd->config->mono; + + ibuf = &state->ibuf[0]; + frames = MIN(frames, ibuf->s_free); + bytes = frames * cd->frame_bytes; + frames_left = frames; + + /* Get pointer to source data in circular buffer, get buffer start and size to + * check for wrap. The size in bytes is converted to number of s16 samples to + * control the samples process loop. If the number of bytes requested is not + * possible, an error is returned. + */ + ret = source_get_data_s16(source, bytes, &x, &x_start, &x_size); + if (ret) + return ret; + + /* Set helper pointers to buffer end for wrap check. Then loop until all + * samples are processed. + */ + x_end = x_start + x_size; + + while (frames_left) { + /* Find out samples to process before first wrap or end of data. */ + ibuf = &state->ibuf[0]; + n1 = (x_end - x) / stream_channels; + n2 = phase_vocoder_buffer_samples_without_wrap(ibuf, ibuf->w_ptr); + n = MIN(n1, n2); + n = MIN(n, frames_left); + if (process_mono) { + for (i = 0; i < n; i++) { + mix = 0; + for (j = 0; j < stream_channels; j++) + mix += Q_MULTSR_32X32((int64_t)mix_gain, *x++, 31, 15, 31); + + *ibuf->w_ptr++ = sat_int32(mix); + } + } else { + for (i = 0; i < n; i++) { + for (j = 0; j < stream_channels; j++) { + ibuf = &state->ibuf[j]; + in = *x++; + *ibuf->w_ptr++ = (int32_t)in << 16; + } + } + } + + /* One of the buffers needs a wrap (or end of data), so check for wrap */ + for (j = 0; j < process_channels; j++) { + ibuf = &state->ibuf[j]; + ibuf->w_ptr = phase_vocoder_buffer_wrap(ibuf, ibuf->w_ptr); + } + + if (x >= x_end) + x -= x_size; + + /* Update processed samples count for next loop iteration. */ + frames_left -= n; + } + + /* Update the source for bytes consumed. Return success. */ + source_release_data(source, bytes); + for (j = 0; j < process_channels; j++) { + ibuf = &state->ibuf[j]; + ibuf->s_avail += frames; + ibuf->s_free -= frames; + } + return 0; +} + +/** + * phase_vocoder_sink_s16() - Write processed samples out as S16_LE. + * @cd: Pointer to component private data. + * @sink: Sink for PCM samples data. + * @frames: Number of audio data frames to produce. + * + * Copies up to @frames frames from the per-channel output ring buffer to + * @sink in S16_LE format with rounding shift from Q1.31 down to Q1.15. In + * mono-mix mode the single processed channel is replicated to all output + * channels. The output ring buffer slots are cleared after read. + * + * Return: Value zero for success, otherwise an error code. + */ +int phase_vocoder_sink_s16(struct phase_vocoder_comp_data *cd, struct sof_sink *sink, int frames) +{ + struct phase_vocoder_state *state = &cd->state; + struct phase_vocoder_buffer *obuf; + int16_t sample; + int16_t *y, *y_start, *y_end; + int frames_remain; + int y_size; + int bytes; + int ret; + int ch, n1, n, i; + int stream_channels = cd->stream_channels; + int process_channels = cd->process_channels; + bool process_mono = cd->config->mono; + + obuf = &state->obuf[0]; + frames = MIN(frames, obuf->s_avail); + if (!frames) + return 0; + + /* Get pointer to sink data in circular buffer */ + bytes = frames * cd->frame_bytes; + frames_remain = frames; + ret = sink_get_buffer_s16(sink, bytes, &y, &y_start, &y_size); + if (ret) + return ret; + + /* Set helper pointers to buffer end for wrap check. Then loop until all + * samples are processed. + */ + y_end = y_start + y_size; + while (frames_remain) { + /* Find out samples to process before first wrap or end of data. */ + obuf = &state->obuf[0]; + n1 = (y_end - y) / stream_channels; + n = phase_vocoder_buffer_samples_without_wrap(obuf, obuf->r_ptr); + n = MIN(n1, n); + n = MIN(n, frames_remain); + + if (process_mono) { + for (i = 0; i < n; i++) { + sample = sat_int16(Q_SHIFT_RND(*obuf->r_ptr, 31, 15)); + for (ch = 0; ch < stream_channels; ch++) + *y++ = sample; + + *obuf->r_ptr++ = 0; /* clear overlap add mix */ + } + } else { + for (i = 0; i < n; i++) { + for (ch = 0; ch < stream_channels; ch++) { + obuf = &state->obuf[ch]; + *y++ = sat_int16(Q_SHIFT_RND(*obuf->r_ptr, 31, 15)); + *obuf->r_ptr++ = 0; /* clear overlap add mix */ + } + } + } + + /* One of the buffers needs a wrap (or end of data), so check for wrap */ + for (ch = 0; ch < process_channels; ch++) { + obuf = &state->obuf[ch]; + obuf->r_ptr = phase_vocoder_buffer_wrap(obuf, obuf->r_ptr); + } + + if (y >= y_end) + y -= y_size; + + /* Update processed samples count for next loop iteration. */ + frames_remain -= n; + } + + /* Update the sink for bytes produced. Return success. */ + sink_commit_buffer(sink, bytes); + for (ch = 0; ch < process_channels; ch++) { + obuf = &state->obuf[ch]; + obuf->s_avail -= frames; + obuf->s_free += frames; + } + + return 0; +} +#endif /* CONFIG_FORMAT_S16LE */ + +void phase_vocoder_fill_fft_buffer(struct phase_vocoder_state *state, int ch) +{ + struct phase_vocoder_buffer *ibuf = &state->ibuf[ch]; + struct phase_vocoder_fft *fft = &state->fft; + struct icomplex32 *fft_buf_ptr; + int32_t *prev_data = state->prev_data[ch]; + int32_t *r = ibuf->r_ptr; + const int prev_data_size = state->prev_data_size; + const int fft_hop_size = fft->fft_hop_size; + int samples_remain = fft_hop_size; + int j; + int n; + + /* Copy overlapped samples from state buffer. Imaginary part of input + * remains zero. + */ + fft_buf_ptr = fft->fft_buf; + for (j = 0; j < prev_data_size; j++) { + *fft_buf_ptr = (struct icomplex32){.real = prev_data[j], .imag = 0}; + fft_buf_ptr++; + } + + /* Copy hop size of new data from circular buffer */ + fft_buf_ptr = &fft->fft_buf[prev_data_size]; + while (samples_remain) { + n = phase_vocoder_buffer_samples_without_wrap(ibuf, r); + n = MIN(n, samples_remain); + for (j = 0; j < n; j++) { + *fft_buf_ptr = (struct icomplex32){.real = *r++, .imag = 0}; + fft_buf_ptr++; + } + r = phase_vocoder_buffer_wrap(ibuf, r); + samples_remain -= n; + } + + ibuf->r_ptr = r; + ibuf->s_avail -= fft_hop_size; + ibuf->s_free += fft_hop_size; + + /* Copy for next time data back to input data overlap buffer */ + fft_buf_ptr = &fft->fft_buf[fft_hop_size]; + for (j = 0; j < prev_data_size; j++) + *prev_data++ = fft_buf_ptr++->real; +} + +int phase_vocoder_overlap_add_ifft_buffer(struct phase_vocoder_state *state, int ch) +{ + struct phase_vocoder_buffer *obuf = &state->obuf[ch]; + struct phase_vocoder_fft *fft = &state->fft; + int32_t *w = obuf->w_ptr; + int32_t sample; + int i; + int n; + int samples_remain = fft->fft_size; + int idx = 0; + + if (obuf->s_free < samples_remain) + return -EINVAL; + + while (samples_remain) { + n = phase_vocoder_buffer_samples_without_wrap(obuf, w); + n = MIN(samples_remain, n); + for (i = 0; i < n; i++) { + sample = Q_MULTSR_32X32((int64_t)state->gain_comp, fft->fft_buf[idx].real, + 31, 31, 31); + *w = sat_int32((int64_t)*w + sample); + w++; + idx++; + } + w = phase_vocoder_buffer_wrap(obuf, w); + samples_remain -= n; + } + + w = obuf->w_ptr + fft->fft_hop_size; + obuf->w_ptr = phase_vocoder_buffer_wrap(obuf, w); + obuf->s_avail += fft->fft_hop_size; + obuf->s_free -= fft->fft_hop_size; + return 0; +} + +void phase_vocoder_apply_window(struct phase_vocoder_state *state) +{ + struct phase_vocoder_fft *fft = &state->fft; + struct icomplex32 *fft_buf_ptr = fft->fft_buf; + const int32_t *window = state->window; + const int fft_size = fft->fft_size; + int i; + + for (i = 0; i < fft_size; i++) { + fft_buf_ptr->real = sat_int32(Q_MULTSR_32X32((int64_t)fft_buf_ptr->real, + window[i], 31, 31, 31)); + fft_buf_ptr++; + } +} diff --git a/src/audio/phase_vocoder/phase_vocoder-ipc4.c b/src/audio/phase_vocoder/phase_vocoder-ipc4.c new file mode 100644 index 000000000000..f28e7d5db833 --- /dev/null +++ b/src/audio/phase_vocoder/phase_vocoder-ipc4.c @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 Intel Corporation. + +#include +#include +#include "phase_vocoder.h" + +LOG_MODULE_DECLARE(phase_vocoder, CONFIG_SOF_LOG_LEVEL); + +/* IPC4 controls handler */ +__cold int phase_vocoder_set_config(struct processing_module *mod, uint32_t param_id, + enum module_cfg_fragment_position pos, + uint32_t data_offset_size, const uint8_t *fragment, + size_t fragment_size, uint8_t *response, size_t response_size) +{ + struct sof_ipc4_control_msg_payload *ctl = (struct sof_ipc4_control_msg_payload *)fragment; + struct phase_vocoder_comp_data *cd = module_get_private_data(mod); + struct comp_dev *dev = mod->dev; + + assert_can_be_cold(); + + switch (param_id) { + case SOF_IPC4_SWITCH_CONTROL_PARAM_ID: + if (ctl->id != 0) { + comp_err(dev, "Illegal switch control id = %d.", ctl->id); + return -EINVAL; + } + + if (ctl->num_elems != 1) { + comp_err(dev, "Illegal switch control num_elems = %d.", ctl->num_elems); + return -EINVAL; + } + + cd->enable = ctl->chanv[0].value; + comp_info(dev, "enable = %d", cd->enable); + return 0; + + case SOF_IPC4_ENUM_CONTROL_PARAM_ID: + if (ctl->id != 0) { + comp_err(dev, "Illegal enum control id = %d.", ctl->id); + return -EINVAL; + } + + if (ctl->num_elems != 1) { + comp_err(dev, "Illegal enum control num_elems = %d.", ctl->num_elems); + return -EINVAL; + } + + if (ctl->chanv[0].value > 15) { + comp_err(dev, "Illegal enum control value = %d.", ctl->chanv[0].value); + return -EINVAL; + } + + cd->speed_enum = ctl->chanv[0].value; + cd->speed_ctrl = PHASE_VOCODER_MIN_SPEED_Q29 + + Q_MULTSR_32X32((int64_t)cd->speed_enum, PHASE_VOCODER_SPEED_STEP_Q31, + 0, 31, 29); + + comp_info(dev, "speed_enum = %d, speed = %d", cd->speed_enum, cd->speed_ctrl); + return 0; + } + + if (fragment_size != sizeof(struct sof_phase_vocoder_config)) { + comp_err(dev, "Illegal fragment size %d, expect %d.", fragment_size, + sizeof(struct sof_phase_vocoder_config)); + return -EINVAL; + } + + if (!cd->config) { + cd->config = mod_alloc(mod, sizeof(struct sof_phase_vocoder_config)); + if (!cd->config) { + comp_err(dev, "Failed to allocate configuration."); + return -ENOMEM; + } + } + + memcpy_s(cd->config, sizeof(struct sof_phase_vocoder_config), fragment, fragment_size); + return 0; +} + +/* Not used in IPC4 systems, if IPC4 only component, omit .get_configuration set */ +__cold int phase_vocoder_get_config(struct processing_module *mod, uint32_t config_id, + uint32_t *data_offset_size, uint8_t *fragment, + size_t fragment_size) +{ + assert_can_be_cold(); + return 0; +} diff --git a/src/audio/phase_vocoder/phase_vocoder.c b/src/audio/phase_vocoder/phase_vocoder.c new file mode 100644 index 000000000000..b82bf700eb9e --- /dev/null +++ b/src/audio/phase_vocoder/phase_vocoder.c @@ -0,0 +1,284 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 Intel Corporation. + +#include +#include +#include +#include +#include +#include "phase_vocoder.h" + +/* UUID identifies the components. Use e.g. command uuidgen from package + * uuid-runtime, add it to uuid-registry.txt in SOF top level. + */ +SOF_DEFINE_REG_UUID(phase_vocoder); + +/* Creates logging data for the component */ +LOG_MODULE_REGISTER(phase_vocoder, CONFIG_SOF_LOG_LEVEL); + +/* Creates the component trace. Traces show in trace console the component + * info, warning, and error messages. + */ +DECLARE_TR_CTX(phase_vocoder_tr, SOF_UUID(phase_vocoder_uuid), LOG_LEVEL_INFO); + +#if STFT_DEBUG +FILE *stft_debug_fft_in_fh; +FILE *stft_debug_fft_out_fh; +FILE *stft_debug_ifft_out_fh; +#endif + +static void phase_vocoder_reset_parameters(struct processing_module *mod) +{ + struct phase_vocoder_comp_data *cd = module_get_private_data(mod); + struct sof_phase_vocoder_config *config = cd->config; + + memset(cd, 0, sizeof(*cd)); + cd->config = config; + cd->enable = false; + cd->speed_ctrl = PHASE_VOCODER_SPEED_NORMAL; + + /* Sync state.speed */ + phase_vocoder_reset_for_new_speed(cd); +} + +/** + * phase_vocoder_init() - Initialize the phase_vocoder component. + * @mod: Pointer to module data. + * + * This function is called when the instance is created. The + * macro __cold informs that the code that is non-critical + * is loaded to slower but large DRAM. + * + * Return: Zero if success, otherwise error code. + */ +__cold static int phase_vocoder_init(struct processing_module *mod) +{ + struct module_data *md = &mod->priv; + struct comp_dev *dev = mod->dev; + struct phase_vocoder_comp_data *cd; + + assert_can_be_cold(); + + comp_info(dev, "phase_vocoder_init()"); + + cd = mod_zalloc(mod, sizeof(*cd)); + if (!cd) + return -ENOMEM; + + md->private = cd; + phase_vocoder_reset_parameters(mod); + +#if STFT_DEBUG + stft_debug_fft_in_fh = fopen("stft_debug_fft_in.txt", "w"); + if (!stft_debug_fft_in_fh) { + fprintf(stderr, "Debug file open failed.\n"); + return -EINVAL; + } + + stft_debug_fft_out_fh = fopen("stft_debug_fft_out.txt", "w"); + if (!stft_debug_fft_out_fh) { + fclose(stft_debug_fft_in_fh); + fprintf(stderr, "Debug file open failed.\n"); + return -EINVAL; + } + + stft_debug_ifft_out_fh = fopen("stft_debug_ifft_out.txt", "w"); + if (!stft_debug_ifft_out_fh) { + fprintf(stderr, "Debug file open failed.\n"); + fclose(stft_debug_fft_in_fh); + fclose(stft_debug_fft_out_fh); + return -EINVAL; + } +#endif + + return 0; +} + +/** + * phase_vocoder_process() - The audio data processing function. + * @mod: Pointer to module data. + * @sources: Pointer to audio samples data sources array. + * @num_of_sources: Number of sources in the array. + * @sinks: Pointer to audio samples data sinks array. + * @num_of_sinks: Number of sinks in the array. + * + * This is the processing function that is called for scheduled + * pipelines. The processing is controlled by the enable switch. + * + * Return: Zero if success, otherwise error code. + */ +static int phase_vocoder_process(struct processing_module *mod, struct sof_source **sources, + int num_of_sources, struct sof_sink **sinks, int num_of_sinks) +{ + struct phase_vocoder_comp_data *cd = module_get_private_data(mod); + struct sof_source *source = sources[0]; /* One input */ + struct sof_sink *sink = sinks[0]; /* One output */ + int source_frames = source_get_data_frames_available(source); + int sink_frames = sink_get_free_frames(sink); + int frames; + int ret; + + if (cd->speed_ctrl != cd->state.speed) + phase_vocoder_reset_for_new_speed(cd); + + if (cd->enable) { + ret = cd->phase_vocoder_func(mod, source, sink, source_frames, sink_frames); + if (ret) + comp_err(mod->dev, "Failure, check the setup parameters."); + + return ret; + } + + /* Just copy from source to sink. */ + frames = MIN(source_frames, sink_frames); + source_to_sink_copy(source, sink, true, frames * cd->frame_bytes); + return 0; +} + +/** + * phase_vocoder_prepare() - Prepare the component for processing. + * @mod: Pointer to module data. + * @sources: Pointer to audio samples data sources array. + * @num_of_sources: Number of sources in the array. + * @sinks: Pointer to audio samples data sinks array. + * @num_of_sinks: Number of sinks in the array. + * + * Function prepare is called just before the pipeline is started. In + * this case the audio format parameters are for better code performance + * saved to component data to avoid to find out them in process. The + * processing function pointer is set to process the current audio format. + * + * Return: Value zero if success, otherwise error code. + */ +static int phase_vocoder_prepare(struct processing_module *mod, struct sof_source **sources, + int num_of_sources, struct sof_sink **sinks, int num_of_sinks) +{ + struct module_data *mod_priv = &mod->priv; + struct ipc4_base_module_cfg *base_cfg = &mod_priv->cfg.base_cfg; + struct phase_vocoder_comp_data *cd = module_get_private_data(mod); + struct comp_dev *dev = mod->dev; + enum sof_ipc_frame source_format; + int ret; + + comp_dbg(dev, "prepare"); + + /* The processing example in this component supports one input and one + * output. Generally there can be more. + */ + if (num_of_sources != 1 || num_of_sinks != 1) { + comp_err(dev, "Only one source and one sink is supported."); + return -EINVAL; + } + + /* Initialize STFT, max_frames is set to dev->frames + 4 */ + if (!cd->config) { + comp_err(dev, "Can't prepare without bytes control configuration."); + return -EINVAL; + } + + /* get source data format */ + cd->frame_bytes = source_get_frame_bytes(sources[0]); + cd->stream_channels = source_get_channels(sources[0]); + cd->sample_rate = source_get_rate(sources[0]); + + /* Note: dev->frames is zero, use ibs */ + cd->max_input_frames = base_cfg->ibs / cd->frame_bytes + PHASE_VOCODER_MAX_FRAMES_MARGIN; + cd->max_output_frames = base_cfg->obs / cd->frame_bytes + PHASE_VOCODER_MAX_FRAMES_MARGIN; + source_format = source_get_valid_fmt(sources[0]); + comp_info(dev, "source_format %d channels %d max_input_frames %d max_output_frames %d", + source_format, cd->stream_channels, cd->max_input_frames, cd->max_output_frames); + + ret = phase_vocoder_setup(mod); + if (ret < 0) { + comp_err(dev, "setup failed."); + return ret; + } + + cd->phase_vocoder_func = phase_vocoder_find_proc_func(source_format); + if (!cd->phase_vocoder_func) { + comp_err(dev, "No processing function found for format %d.", source_format); + return -EINVAL; + } + + return 0; +} + +/** + * phase_vocoder_reset() - Reset the component. + * @mod: Pointer to module data. + * + * The component reset is called when pipeline is stopped. The reset + * should return the component to same state as init. + * + * Return: Value zero, always success. + */ +static int phase_vocoder_reset(struct processing_module *mod) +{ + comp_dbg(mod->dev, "reset"); + + phase_vocoder_free_buffers(mod); + phase_vocoder_reset_parameters(mod); + return 0; +} + +/** + * phase_vocoder_free() - Free dynamic allocations. + * @mod: Pointer to module data. + * + * Component free is called when the pipelines are deleted. All + * dynamic allocations need to be freed here. The macro __cold + * instructs the build to locate this performance wise non-critical + * function to large and slower DRAM. + * + * Return: Value zero, always success. + */ +__cold static int phase_vocoder_free(struct processing_module *mod) +{ + struct phase_vocoder_comp_data *cd = module_get_private_data(mod); + + assert_can_be_cold(); + + comp_dbg(mod->dev, "free"); + mod_free(mod, cd->config); + mod_free(mod, cd); + +#if STFT_DEBUG + fclose(stft_debug_fft_in_fh); + fclose(stft_debug_fft_out_fh); + fclose(stft_debug_ifft_out_fh); +#endif + return 0; +} + +/* This defines the module operations */ +static const struct module_interface phase_vocoder_interface = { + .init = phase_vocoder_init, + .prepare = phase_vocoder_prepare, + .process = phase_vocoder_process, + .set_configuration = phase_vocoder_set_config, + .get_configuration = phase_vocoder_get_config, + .reset = phase_vocoder_reset, + .free = phase_vocoder_free}; + +/* This controls build of the module. If COMP_MODULE is selected in kconfig + * this is build as dynamically loadable module. + */ +#if CONFIG_COMP_PHASE_VOCODER_MODULE + +#include +#include +#include + +static const struct sof_man_module_manifest mod_manifest __section(".module") __used = + SOF_LLEXT_MODULE_MANIFEST("PHASEVOC", &phase_vocoder_interface, 1, + SOF_REG_UUID(phase_vocoder), 40); + +SOF_LLEXT_BUILDINFO; + +#else + +DECLARE_MODULE_ADAPTER(phase_vocoder_interface, phase_vocoder_uuid, phase_vocoder_tr); +SOF_MODULE_INIT(phase_vocoder, sys_comp_module_phase_vocoder_interface_init); + +#endif diff --git a/src/audio/phase_vocoder/phase_vocoder.h b/src/audio/phase_vocoder/phase_vocoder.h new file mode 100644 index 000000000000..fbf95fdeeeec --- /dev/null +++ b/src/audio/phase_vocoder/phase_vocoder.h @@ -0,0 +1,334 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2026 Intel Corporation. + * + */ +#ifndef __SOF_AUDIO_PHASE_VOCODER_H__ +#define __SOF_AUDIO_PHASE_VOCODER_H__ + +#include +#include +#include +#include + +#include +#include + +#if CONFIG_LIBRARY +#define STFT_DEBUG 0 /* Keep zero, produces large files with fprintf() */ +#else +#define STFT_DEBUG 0 +#endif + +#define PHASE_VOCODER_MAX_FRAMES_MARGIN 0 /* Adds to buffer size */ +#define PHASE_VOCODER_MIN_SPEED_Q29 Q_CONVERT_FLOAT(0.5, 29) /* Min. speed is 0.5 */ +#define PHASE_VOCODER_MAX_SPEED_Q29 Q_CONVERT_FLOAT(2.0, 29) /* Max. speed is 2.0 */ +#define PHASE_VOCODER_SPEED_STEP_Q31 Q_CONVERT_FLOAT((2 - 0.5) / 15, 31) /* Steps for enum ctrl */ +#define PHASE_VOCODER_SPEED_NORMAL Q_CONVERT_FLOAT(1.0, 29) /* Default to speed 1 */ +#define PHASE_VOCODER_ONE_Q29 Q_CONVERT_FLOAT(1.0, 29) /* One as Q29 */ +#define PHASE_VOCODER_HALF_Q29 Q_CONVERT_FLOAT(0.5, 29) /* 0.5 as Q29 */ + +#define PHASE_VOCODER_PI_Q28 843314857 /* int32(pi * 2^28), Q28 */ +#define PHASE_VOCODER_TWO_PI_Q28 1686629713 /* int32(2 * pi * 2^28), Q28 */ + +#define PHASE_VOCODER_PI_Q27 421657428 /* int32(pi * 2^27) */ +#define PHASE_VOCODER_TWO_PI_Q27 843314857 /* int32(2 * pi * 2^27) */ + +enum sof_phase_vocoder_fft_window_type { + STFT_RECTANGULAR_WINDOW = 0, + STFT_BLACKMAN_WINDOW = 1, + STFT_HAMMING_WINDOW = 2, + STFT_HANN_WINDOW = 3, +}; + +/** + * struct sof_phase_vocoder_config - IPC configuration blob for phase vocoder + * @size: Size of this struct in bytes + * @reserved: Reserved for future use + * @sample_frequency: Sample rate in Hz, e.g. 16000 + * @window_gain_comp: Q1.31 gain for IFFT + * @reserved_32: Reserved for future use + * @mono: Set to 1 for mono, zero for all channels + * @frame_length: Frame length in samples, e.g. 400 for 25 ms @ 16 kHz + * @frame_shift: Frame shift in samples, e.g. 160 for 10 ms @ 16 kHz + * @reserved_16: Reserved for future use + * @reserved_pad: Reserved for future use, kept for ABI compatibility + * @window: Window type, use RECTANGULAR_WINDOW, etc. + */ +struct sof_phase_vocoder_config { + uint32_t size; + uint32_t reserved[8]; + int32_t sample_frequency; + int32_t window_gain_comp; + int32_t reserved_32; + int16_t mono; + int16_t frame_length; + int16_t frame_shift; + int16_t reserved_16; + int32_t reserved_pad; + enum sof_phase_vocoder_fft_window_type window; +} __attribute__((packed)); + +/** + * struct phase_vocoder_buffer - Circular buffer for phase vocoder audio data + * @addr: Start address of the buffer + * @end_addr: End address of the buffer + * @r_ptr: Read pointer + * @w_ptr: Write pointer + * @s_avail: Available samples count + * @s_free: Free samples count + * @s_length: Buffer length in samples for wrap + */ +struct phase_vocoder_buffer { + int32_t *addr; + int32_t *end_addr; + int32_t *r_ptr; + int32_t *w_ptr; + int s_avail; + int s_free; + int s_length; +}; + +/** + * struct phase_vocoder_fft - FFT processing state + * @fft_buf: FFT input buffer, size is fft_size + * @fft_out: FFT output buffer, size is fft_size + * @fft_plan: FFT plan instance + * @ifft_plan: Inverse FFT plan instance + * @fft_size: FFT length in samples + * @fft_hop_size: FFT hop size in samples + * @half_fft_size: Half of the FFT size + * @fft_buffer_size: FFT buffer size in bytes + */ +struct phase_vocoder_fft { + struct icomplex32 *fft_buf; + struct icomplex32 *fft_out; + struct fft_plan *fft_plan; + struct fft_plan *ifft_plan; + int fft_size; + int fft_hop_size; + int half_fft_size; + size_t fft_buffer_size; +}; + +/** + * struct phase_vocoder_polar - Polar domain processing data + * @polar: Current polar representation per channel + * @polar_prev: Previous polar representation per channel + * @polar_tmp: Temporary polar buffer + * @polar_buffer: Base address of the joint polar/angle/output-phase allocation + * @angle_delta_prev: Previous angle delta per channel + * @angle_delta: Current angle delta per channel + * @output_phase: Output phase per channel + */ +struct phase_vocoder_polar { + struct ipolar32 *polar[PLATFORM_MAX_CHANNELS]; + struct ipolar32 *polar_prev[PLATFORM_MAX_CHANNELS]; + struct ipolar32 *polar_tmp; + int32_t *polar_buffer; + int32_t *angle_delta_prev[PLATFORM_MAX_CHANNELS]; + int32_t *angle_delta[PLATFORM_MAX_CHANNELS]; + int32_t *output_phase[PLATFORM_MAX_CHANNELS]; +}; + +/** + * struct phase_vocoder_state - Phase vocoder algorithm state + * @ibuf: Circular buffers for input data, per channel + * @obuf: Circular buffers for output data, per channel + * @fft: FFT instance, common for all channels + * @polar: Processing state in polar domain + * @phase_vocoder_polar_bytes: Size in bytes of the joint polar allocation + * @prev_data: Previous frame data per channel, size is prev_data_size + * @buffers: Pointer to allocated memory for all buffers + * @window: Window function coefficients, size is fft_size + * @num_input_fft_to_use: Number of input FFTs to use for processing + * @num_input_fft: Total input FFTs count + * @num_output_ifft: Total output IFFTs count + * @gain_comp: Gain to compensate window gain + * @interpolate_fraction: Q3.29 interpolation coefficient + * @speed: Q3.29 actual render speed + * @prev_data_size: Size of previous data buffer + * @first_output_ifft_done: True after first output IFFT is completed + */ +struct phase_vocoder_state { + struct phase_vocoder_buffer ibuf[PLATFORM_MAX_CHANNELS]; + struct phase_vocoder_buffer obuf[PLATFORM_MAX_CHANNELS]; + struct phase_vocoder_fft fft; + struct phase_vocoder_polar polar; + size_t phase_vocoder_polar_bytes; + int32_t *prev_data[PLATFORM_MAX_CHANNELS]; + int32_t *buffers; + int32_t *window; + int32_t num_input_fft_to_use; + int32_t num_input_fft; + int32_t num_output_ifft; + int32_t gain_comp; + int32_t interpolate_fraction; + int32_t speed; + int prev_data_size; + bool first_output_ifft_done; +}; + +/** + * typedef phase_vocoder_func - Function pointer for process function + * @mod: Pointer to module data. + * @source: Source for PCM samples data. + * @sink: Sink for PCM samples data. + * @source_frames: Number of source audio data frames to process. + * @sink_frames: Number of sink audio data frames to produce. + */ +typedef int (*phase_vocoder_func)(const struct processing_module *mod, struct sof_source *source, + struct sof_sink *sink, uint32_t source_frames, + uint32_t sink_frames); + +/** + * struct phase_vocoder_comp_data - Phase vocoder component data + * @phase_vocoder_func: Pointer to used processing function. + * @state: Phase vocoder algorithm state. + * @config: Configuration blob for the module. + * @mono_mix_coef: Gain for channel for mixing, Q1.15 + * @sample_rate: Audio sample rate in Hz + * @speed_ctrl: Speed Q3.29, allowed range 0.5 to 2.0 + * @speed_enum: Speed control value as enum 0-15 + * @frame_bytes: Number of bytes in an audio frame. + * @max_input_frames: Maximum number of input frames per processing call + * @max_output_frames: Maximum number of output frames per processing call + * @stream_channels: Channels count to consume/produce + * @process_channels: Channels count to process. + * @enable: Control processing on/off, off is pass-through + */ +struct phase_vocoder_comp_data { + phase_vocoder_func phase_vocoder_func; + struct phase_vocoder_state state; + struct sof_phase_vocoder_config *config; + int32_t mono_mix_coef; + int32_t sample_rate; + int32_t speed_ctrl; + int32_t speed_enum; + size_t frame_bytes; + int max_input_frames; + int max_output_frames; + int stream_channels; + int process_channels; + bool enable; +}; + +static inline int phase_vocoder_buffer_samples_without_wrap(struct phase_vocoder_buffer *buffer, + int32_t *ptr) +{ + return buffer->end_addr - ptr; +} + +static inline int32_t *phase_vocoder_buffer_wrap(struct phase_vocoder_buffer *buffer, int32_t *ptr) +{ + if (ptr >= buffer->end_addr) + ptr -= buffer->s_length; + + return ptr; +} + +/** + * struct phase_vocoder_proc_fnmap - processing functions for frame formats + * @frame_fmt: Current frame format + * @phase_vocoder_proc_func: Function pointer for the suitable processing function + */ +struct phase_vocoder_proc_fnmap { + enum sof_ipc_frame frame_fmt; + phase_vocoder_func phase_vocoder_function; +}; + +/** + * phase_vocoder_find_proc_func() - Find suitable processing function. + * @src_fmt: Enum value for PCM format. + * + * This function finds the suitable processing function to use for + * the used PCM format. If not found, return NULL. + * + * Return: Pointer to processing function for the requested PCM format. + */ +phase_vocoder_func phase_vocoder_find_proc_func(enum sof_ipc_frame src_fmt); + +#if CONFIG_IPC_MAJOR_4 +/** + * phase_vocoder_set_config() - Handle controls set + * @mod: Pointer to module data. + * @param_id: Id to know control type, used to know ALSA control type. + * @pos: Position of the fragment in the large message. + * @data_offset_size: Size of the whole configuration if it is the first or only + * fragment. Otherwise it is offset of the fragment. + * @fragment: Message payload data. + * @fragment_size: Size of this fragment. + * @response_size: Size of response. + * + * This function handles the real-time controls. The ALSA controls have the + * param_id set to indicate the control type. The control ID, from topology, + * is used to separate the controls instances of same type. In control payload + * the num_elems defines to how many channels the control is applied to. + * + * Return: Zero if success, otherwise error code. + */ +int phase_vocoder_set_config(struct processing_module *mod, uint32_t param_id, + enum module_cfg_fragment_position pos, uint32_t data_offset_size, + const uint8_t *fragment, size_t fragment_size, uint8_t *response, + size_t response_size); +/** + * phase_vocoder_get_config() - Handle controls get + * @mod: Pointer to module data. + * @config_id: Configuration ID. + * @data_offset_size: Size of the whole configuration if it is the first or only + * fragment. Otherwise it is offset of the fragment. + * @fragment: Message payload data. + * @fragment_size: Size of this fragment. + * + * This function is used for controls get. + * + * Return: Zero if success, otherwise error code. + */ +int phase_vocoder_get_config(struct processing_module *mod, uint32_t config_id, + uint32_t *data_offset_size, uint8_t *fragment, size_t fragment_size); +#else +static inline int phase_vocoder_set_config(struct processing_module *mod, uint32_t param_id, + enum module_cfg_fragment_position pos, + uint32_t data_offset_size, const uint8_t *fragment, + size_t fragment_size, uint8_t *response, + size_t response_size) +{ + return 0; +} + +static inline int phase_vocoder_get_config(struct processing_module *mod, uint32_t config_id, + uint32_t *data_offset_size, uint8_t *fragment, + size_t fragment_size) +{ + return 0; +} +#endif + +void phase_vocoder_apply_window(struct phase_vocoder_state *state); + +void phase_vocoder_fill_fft_buffer(struct phase_vocoder_state *state, int ch); + +void phase_vocoder_free_buffers(struct processing_module *mod); + +int phase_vocoder_overlap_add_ifft_buffer(struct phase_vocoder_state *state, int ch); + +void phase_vocoder_reset_for_new_speed(struct phase_vocoder_comp_data *cd); + +int phase_vocoder_setup(struct processing_module *mod); + +int phase_vocoder_sink_s16(struct phase_vocoder_comp_data *cd, struct sof_sink *sink, int frames); + +int phase_vocoder_sink_s24(struct phase_vocoder_comp_data *cd, struct sof_sink *sink, int frames); + +int phase_vocoder_sink_s32(struct phase_vocoder_comp_data *cd, struct sof_sink *sink, int frames); + +int phase_vocoder_source_s16(struct phase_vocoder_comp_data *cd, struct sof_source *source, + int frames); + +int phase_vocoder_source_s24(struct phase_vocoder_comp_data *cd, struct sof_source *source, + int frames); + +int phase_vocoder_source_s32(struct phase_vocoder_comp_data *cd, struct sof_source *source, + int frames); + +#endif // __SOF_AUDIO_PHASE_VOCODER_H__ diff --git a/src/audio/phase_vocoder/phase_vocoder.toml b/src/audio/phase_vocoder/phase_vocoder.toml new file mode 100644 index 000000000000..9ec249fe01a5 --- /dev/null +++ b/src/audio/phase_vocoder/phase_vocoder.toml @@ -0,0 +1,21 @@ +#ifndef LOAD_TYPE +#define LOAD_TYPE "0" +#endif + + REM # Template component module config + [[module.entry]] + name = "PHASEVOC" + uuid = UUIDREG_STR_PHASE_VOCODER + affinity_mask = "0x1" + instance_count = "40" + domain_types = "0" + load_type = LOAD_TYPE + module_type = "9" + auto_start = "0" + sched_caps = [1, 0x00008000] + REM # pin = [dir, type, sample rate, size, container, channel-cfg] + pin = [0, 0, 0xfeef, 0xf, 0xf, 0x45ff, 1, 0, 0xfeef, 0xf, 0xf, 0x1ff] + REM # mod_cfg [PAR_0 PAR_1 PAR_2 PAR_3 IS_BYTES CPS IBS OBS MOD_FLAGS CPC OBLS] + mod_cfg = [0, 0, 0, 0, 4096, 1000000, 128, 128, 0, 0, 0] + + index = __COUNTER__ diff --git a/src/audio/phase_vocoder/phase_vocoder_common.c b/src/audio/phase_vocoder/phase_vocoder_common.c new file mode 100644 index 000000000000..d21f4e9e90e5 --- /dev/null +++ b/src/audio/phase_vocoder/phase_vocoder_common.c @@ -0,0 +1,588 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 Intel Corporation. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "phase_vocoder.h" + +#include +#include +#include + +#if STFT_DEBUG +extern FILE *stft_debug_fft_in_fh; +extern FILE *stft_debug_fft_out_fh; +extern FILE *stft_debug_ifft_out_fh; + +static void debug_print_to_file_real(FILE *fh, struct icomplex32 *c, int n) +{ + for (int i = 0; i < n; i++) + fprintf(fh, "%d\n", c[i].real); +} + +static void debug_print_to_file_complex(FILE *fh, struct icomplex32 *c, int n) +{ + for (int i = 0; i < n; i++) + fprintf(fh, "%d %d\n", c[i].real, c[i].imag); +} +#endif + +LOG_MODULE_REGISTER(phase_vocoder_common, CONFIG_SOF_LOG_LEVEL); + +static int stft_get_num_ffts_avail(struct phase_vocoder_state *state, int channel) +{ + struct phase_vocoder_buffer *ibuf = &state->ibuf[channel]; + struct phase_vocoder_fft *fft = &state->fft; + + /* Wait for FFT hop size of new data */ + return ibuf->s_avail / fft->fft_hop_size; +} + +static void stft_do_fft(struct phase_vocoder_state *state, int ch) +{ + struct phase_vocoder_fft *fft = &state->fft; + + /* Copy data to FFT input buffer from overlap buffer and from new samples buffer */ + phase_vocoder_fill_fft_buffer(state, ch); + + /* Window function */ + phase_vocoder_apply_window(state); + +#if STFT_DEBUG + debug_print_to_file_real(stft_debug_fft_in_fh, fft->fft_buf, fft->fft_size); +#endif + + /* Compute FFT. A full scale s16 sine input with 2^N samples period in low + * part of s32 real part and zero imaginary part gives to output about 0.5 + * full scale 32 bit output to real and imaginary. The scaling is same for + * all FFT sizes. + */ + fft_execute_32(fft->fft_plan, false); + +#if STFT_DEBUG + debug_print_to_file_complex(stft_debug_fft_out_fh, fft->fft_out, fft->fft_size); +#endif +} + +static int stft_do_ifft(struct phase_vocoder_state *state, int ch) +{ + struct phase_vocoder_fft *fft = &state->fft; + + /* Compute IFFT */ + fft_execute_32(fft->ifft_plan, true); + +#if STFT_DEBUG + debug_print_to_file_complex(stft_debug_ifft_out_fh, fft->fft_buf, fft->fft_size); +#endif + + /* Window function */ + phase_vocoder_apply_window(state); + + /* Copy to output buffer */ + return phase_vocoder_overlap_add_ifft_buffer(state, ch); +} + +/** + * stft_convert_to_polar() - Convert FFT output to polar format + * @fft: FFT state containing the FFT output buffer and parameters as Q1.31. + * @polar_data: Output buffer for polar data, size is half of FFT size. + * Magnitude is Q2.30 and phase is Q5.27. + */ +static void stft_convert_to_polar(struct phase_vocoder_fft *fft, struct ipolar32 *polar_data) +{ + int i; + + /* Get magnitude and phase, convert phase from Q3.29 to Q5.27. */ + for (i = 0; i < fft->half_fft_size; i++) { + sofm_icomplex32_to_polar(&fft->fft_out[i], &polar_data[i]); + polar_data[i].angle = Q_SHIFT_RND(polar_data[i].angle, 29, 27); + } +} + +/** + * stft_convert_to_complex() - Convert polar data back to complex format for IFFT + * @polar_data: Input polar data, size is half of FFT size. + * Magnitude is Q2.30 and phase is Q5.27. + * @fft: FFT state containing the output buffer where complex data will + * be stored and parameters as Q1.31. + */ +static void stft_convert_to_complex(struct ipolar32 *polar_data, struct phase_vocoder_fft *fft) +{ + int i; + + /* Convert phase from Q5.27 to Q3.29, and then to (re, im) complex format as Q1.31*/ + for (i = 0; i < fft->half_fft_size; i++) { + polar_data[i].angle = Q_SHIFT_LEFT(polar_data[i].angle, 27, 29); + sofm_ipolar32_to_complex(&polar_data[i], &fft->fft_out[i]); + } +} + +static void stft_apply_fft_symmetry(struct phase_vocoder_fft *fft) +{ + int i, j, k; + + j = 2 * fft->half_fft_size - 2; + for (i = fft->half_fft_size; i < fft->fft_size; i++) { + k = j - i; + fft->fft_out[i].real = fft->fft_out[k].real; + fft->fft_out[i].imag = -fft->fft_out[k].imag; + } +} + +/** + * phase_vocoder_normalize_counters - Prevent int32 overflow in frame counters + * + * The interpolation fraction depends on (num_output_ifft * speed) mod 2^29. + * By subtracting a multiple of the fraction cycle period from num_output_ifft + * (and the corresponding integer amount from num_input_fft), the fraction is + * preserved exactly while keeping both counters small. + */ +static void phase_vocoder_normalize_counters(struct phase_vocoder_state *state, int32_t speed) +{ + int32_t g; + int32_t output_period; + int32_t delta_output; + int64_t delta_input_scaled; + + if (state->num_output_ifft < (1 << 28)) + return; + + /* The fraction cycle repeats every (2^29 / gcd(speed, 2^29)) output IFFTs */ + g = gcd(speed, 1 << 29); + output_period = (1 << 29) / g; + + /* Subtract the largest multiple of output_period that fits */ + delta_output = (state->num_output_ifft / output_period) * output_period; + if (!delta_output) + return; + + /* delta_output * speed is a multiple of 2^29 by construction, + * so the interpolation fraction is exactly preserved. + */ + delta_input_scaled = (int64_t)delta_output * speed; + + state->num_output_ifft -= delta_output; + state->num_input_fft -= (int32_t)(delta_input_scaled >> 29); +} + +static void phase_vocoder_interpolation_parameters(struct phase_vocoder_comp_data *cd) +{ + struct phase_vocoder_state *state = &cd->state; + int64_t input_frame_num_frac; + int32_t input_frame_num_floor; + + phase_vocoder_normalize_counters(state, cd->state.speed); + + input_frame_num_frac = (int64_t)state->num_output_ifft * cd->state.speed; /* Q31.29 */ + input_frame_num_floor = (int32_t)(input_frame_num_frac >> 29); /* Use floor, not round */ + state->num_input_fft_to_use = input_frame_num_floor + 1; + state->interpolate_fraction = input_frame_num_frac - ((int64_t)input_frame_num_floor << 29); +} + +#if 0 +static int32_t unwrap_angle(int32_t angle) +{ + if (angle > PHASE_VOCODER_PI_Q28) + return angle - PHASE_VOCODER_TWO_PI_Q28; + else if (angle < -PHASE_VOCODER_PI_Q28) + return angle + PHASE_VOCODER_TWO_PI_Q28; + else + return angle; +} +#endif + +static int32_t unwrap_angle_q27(int32_t angle) +{ + while (angle > PHASE_VOCODER_PI_Q27) + angle -= PHASE_VOCODER_TWO_PI_Q27; + + while (angle < -PHASE_VOCODER_PI_Q27) + angle += PHASE_VOCODER_TWO_PI_Q27; + + return angle; +} + +void phase_vocoder_reset_for_new_speed(struct phase_vocoder_comp_data *cd) +{ + struct phase_vocoder_state *state = &cd->state; + + state->speed = cd->speed_ctrl; + + /* Keep num_input_fft at 1, not 0, to avoid re-entering the first + * analysis FFT cold-start path. That path sets angle_delta to the + * absolute phase angle (not a delta between frames). For non-unity + * speeds, multiple IFFTs are produced per input FFT, and the + * absolute angle gets repeatedly accumulated into output_phase + * causing random inter-bin phase incoherence and gain variation. + * + * By setting num_input_fft = 1 the existing polar, angle_delta, + * and output_phase state from the last processing step is preserved. + * These contain proper phase deltas, so interpolation continues + * correctly at the new speed. + */ + state->num_input_fft = 1; + state->num_output_ifft = 0; +} + +static void copy_polar_angles(int32_t *angle_delta_ch, struct ipolar32 *polar_data_ch, + int num_angles) +{ + int i; + + for (i = 0; i < num_angles; i++) + angle_delta_ch[i] = polar_data_ch[i].angle; +} + +// TODO: Civilized input and output counters reset to prevent int32_t wrap +static int stft_do_fft_ifft(const struct processing_module *mod) +{ + struct phase_vocoder_comp_data *cd = module_get_private_data(mod); + struct phase_vocoder_state *state = &cd->state; + struct phase_vocoder_polar *polar = &state->polar; + struct phase_vocoder_fft *fft = &state->fft; + struct ipolar32 *polar_data_prev_ch; + struct ipolar32 *polar_data_ch; + int32_t *angle_delta_prev_ch; + int32_t *angle_delta_ch; + int32_t *output_phase_ch; + int32_t one_minus_frac; + int32_t frac; + int32_t p1, p2; + int32_t a; + const size_t polar_fft_half_bytes = sizeof(struct ipolar32) * fft->half_fft_size; + const size_t int32_fft_half_bytes = sizeof(int32_t) * fft->half_fft_size; + int num_fft; + int ret = 0; + int ch; + int i; + + num_fft = stft_get_num_ffts_avail(state, 0); + if (!num_fft) + return 0; + + /* First analysis FFT */ + if (!state->num_input_fft) { + for (ch = 0; ch < cd->process_channels; ch++) { + stft_do_fft(state, ch); + + /* Convert half-FFT to polar. Magnitude is Q2.30 and phase + * angle is Q5.27. + */ + polar_data_ch = polar->polar[ch]; /* struct ipolar32 */ + stft_convert_to_polar(&state->fft, polar_data_ch); + + /* Initialize output phase directly to the absolute phase + * of the first frame. This avoids the accumulation path + * which would double-count the initial phase when it + * later moves from angle_delta to angle_delta_prev. + */ + output_phase_ch = polar->output_phase[ch]; + copy_polar_angles(output_phase_ch, polar_data_ch, fft->half_fft_size); + + /* Initialize prev polar to same data so magnitude + * interpolation works before second FFT is consumed. + * Leave angle_delta and angle_delta_prev at zero so the + * phase does not advance until real deltas are computed. + */ + memcpy(polar->polar_prev[ch], polar_data_ch, polar_fft_half_bytes); + } + state->num_input_fft++; + num_fft--; + } + + phase_vocoder_interpolation_parameters(cd); + while (state->num_input_fft < state->num_input_fft_to_use && num_fft > 0) { + for (ch = 0; ch < cd->process_channels; ch++) { + stft_do_fft(state, ch); + + /* Update previous polar data. + * Note: Not using memcpy_s() since this is hot algorithm code. + */ + polar_data_prev_ch = polar->polar_prev[ch]; + polar_data_ch = polar->polar[ch]; + memcpy(polar_data_prev_ch, polar_data_ch, polar_fft_half_bytes); + + /* Convert half-FFT to polar. Magnitude is Q2.30 and phase + * angle is Q5.27. + */ + stft_convert_to_polar(&state->fft, polar_data_ch); + + /* Update previous delta phase data */ + angle_delta_ch = polar->angle_delta[ch]; + angle_delta_prev_ch = polar->angle_delta_prev[ch]; + memcpy(angle_delta_prev_ch, angle_delta_ch, int32_fft_half_bytes); + + /* Calculate new delta phase */ + for (i = 0; i < fft->half_fft_size; i++) { + a = polar_data_ch[i].angle - polar_data_prev_ch[i].angle; + angle_delta_ch[i] = unwrap_angle_q27(a); + } + } + state->num_input_fft++; + num_fft--; + } + + if (state->num_input_fft < state->num_input_fft_to_use) + return 0; + + /* Interpolate IFFT frame */ + frac = state->interpolate_fraction; + one_minus_frac = PHASE_VOCODER_ONE_Q29 - frac; + + for (ch = 0; ch < cd->process_channels; ch++) { + polar_data_prev_ch = polar->polar_prev[ch]; + polar_data_ch = polar->polar[ch]; + angle_delta_ch = polar->angle_delta[ch]; + angle_delta_prev_ch = polar->angle_delta_prev[ch]; + output_phase_ch = polar->output_phase[ch]; + + for (i = 0; i < fft->half_fft_size; i++) { + p1 = Q_MULTSR_32X32((int64_t)one_minus_frac, + polar_data_prev_ch[i].magnitude, 29, 30, 30); + p2 = Q_MULTSR_32X32((int64_t)frac, polar_data_ch[i].magnitude, 29, 30, 30); + polar->polar_tmp[i].magnitude = p1 + p2; + + a = output_phase_ch[i]; + p1 = Q_MULTSR_32X32((int64_t)one_minus_frac, angle_delta_prev_ch[i], 29, 27, + 27); + p2 = Q_MULTSR_32X32((int64_t)frac, angle_delta_ch[i], 29, 27, 27); + a = output_phase_ch[i] + p1 + p2; + output_phase_ch[i] = unwrap_angle_q27(a); + polar->polar_tmp[i].angle = output_phase_ch[i]; + } + + /* Convert back to (re, im) complex, and fix upper part */ + stft_convert_to_complex(polar->polar_tmp, &state->fft); + stft_apply_fft_symmetry(&state->fft); + ret = stft_do_ifft(state, ch); + if (ret) { + comp_err(mod->dev, "IFFT failure, check output overlap-add buffer size"); + return ret; + } + } + + comp_dbg(mod->dev, "no = %d, ni = %d, frac = %d", state->num_output_ifft, + state->num_input_fft, frac); + state->num_output_ifft++; + state->first_output_ifft_done = true; + return 0; +} + +static int phase_vocoder_check_fft_run_need(struct phase_vocoder_comp_data *cd) +{ + return cd->state.obuf[0].s_avail < cd->state.fft.fft_hop_size; +} + +#if CONFIG_FORMAT_S32LE || CONFIG_FORMAT_S24LE +static int phase_vocoder_output_zeros_s32(struct phase_vocoder_comp_data *cd, struct sof_sink *sink, + int frames) +{ + int32_t *y, *y_start, *y_end; + int samples = frames * cd->stream_channels; + size_t bytes = samples * sizeof(int32_t); + int samples_without_wrap; + int y_size; + int ret; + + /* Get pointer to sink data in circular buffer, buffer start and size. */ + ret = sink_get_buffer_s32(sink, bytes, &y, &y_start, &y_size); + if (ret) + return ret; + + /* Set helper pointers to buffer end for wrap check. Then loop until all + * samples are processed. + */ + y_end = y_start + y_size; + while (samples) { + /* Find out samples to process before first wrap or end of data. */ + samples_without_wrap = y_end - y; + samples_without_wrap = MIN(samples_without_wrap, samples); + memset(y, 0, samples_without_wrap * sizeof(int32_t)); + y += samples_without_wrap; + + /* Check for wrap */ + if (y >= y_end) + y -= y_size; + + /* Update processed samples count for next loop iteration. */ + samples -= samples_without_wrap; + } + + /* Update the source and sink for bytes consumed and produced. Return success. */ + sink_commit_buffer(sink, bytes); + return 0; +} +#endif + +#if CONFIG_FORMAT_S32LE +static int phase_vocoder_s32(const struct processing_module *mod, struct sof_source *source, + struct sof_sink *sink, uint32_t source_frames, uint32_t sink_frames) +{ + struct phase_vocoder_comp_data *cd = module_get_private_data(mod); + int ret; + + if (phase_vocoder_check_fft_run_need(cd)) { + /* Get samples from source buffer */ + ret = phase_vocoder_source_s32(cd, source, source_frames); + if (ret) + return ret; + + /* Do STFT, processing and inverse STFT */ + ret = stft_do_fft_ifft(mod); + if (ret) + return ret; + } + + /* Get samples from source buffer */ + if (cd->state.first_output_ifft_done) + ret = phase_vocoder_sink_s32(cd, sink, sink_frames); + else + ret = phase_vocoder_output_zeros_s32(cd, sink, sink_frames); + + return ret; +} +#endif /* CONFIG_FORMAT_S32LE */ + +#if CONFIG_FORMAT_S24LE +static int phase_vocoder_s24(const struct processing_module *mod, struct sof_source *source, + struct sof_sink *sink, uint32_t source_frames, uint32_t sink_frames) +{ + struct phase_vocoder_comp_data *cd = module_get_private_data(mod); + int ret; + + if (phase_vocoder_check_fft_run_need(cd)) { + /* Get samples from source buffer */ + ret = phase_vocoder_source_s24(cd, source, source_frames); + if (ret) + return ret; + + /* Do STFT, processing and inverse STFT */ + ret = stft_do_fft_ifft(mod); + if (ret) + return ret; + } + + /* Get samples from source buffer */ + if (cd->state.first_output_ifft_done) + ret = phase_vocoder_sink_s24(cd, sink, sink_frames); + else + /* Use the s32 function */ + ret = phase_vocoder_output_zeros_s32(cd, sink, sink_frames); + + return ret; +} +#endif /* CONFIG_FORMAT_S24LE */ + +#if CONFIG_FORMAT_S16LE +static int phase_vocoder_output_zeros_s16(struct phase_vocoder_comp_data *cd, struct sof_sink *sink, + int frames) +{ + int16_t *y, *y_start, *y_end; + int samples = frames * cd->stream_channels; + size_t bytes = samples * sizeof(int16_t); + int samples_without_wrap; + int y_size; + int ret; + + /* Get pointer to sink data in circular buffer, buffer start and size. */ + ret = sink_get_buffer_s16(sink, bytes, &y, &y_start, &y_size); + if (ret) + return ret; + + /* Set helper pointers to buffer end for wrap check. Then loop until all + * samples are processed. + */ + y_end = y_start + y_size; + while (samples) { + /* Find out samples to process before first wrap or end of data. */ + samples_without_wrap = y_end - y; + samples_without_wrap = MIN(samples_without_wrap, samples); + memset(y, 0, samples_without_wrap * sizeof(int16_t)); + y += samples_without_wrap; + + /* Check for wrap */ + if (y >= y_end) + y -= y_size; + + /* Update processed samples count for next loop iteration. */ + samples -= samples_without_wrap; + } + + /* Update the source and sink for bytes consumed and produced. Return success. */ + sink_commit_buffer(sink, bytes); + return 0; +} + +static int phase_vocoder_s16(const struct processing_module *mod, struct sof_source *source, + struct sof_sink *sink, uint32_t source_frames, uint32_t sink_frames) +{ + struct phase_vocoder_comp_data *cd = module_get_private_data(mod); + int ret; + + if (phase_vocoder_check_fft_run_need(cd)) { + /* Get samples from source buffer */ + ret = phase_vocoder_source_s16(cd, source, source_frames); + if (ret) + return ret; + + /* Do STFT, processing and inverse STFT */ + ret = stft_do_fft_ifft(mod); + if (ret) + return ret; + } + + /* Get samples from source buffer */ + if (cd->state.first_output_ifft_done) + ret = phase_vocoder_sink_s16(cd, sink, sink_frames); + else + ret = phase_vocoder_output_zeros_s16(cd, sink, sink_frames); + + return ret; +} +#endif /* CONFIG_FORMAT_S16LE */ + +/* This struct array defines the used processing functions for + * the PCM formats. + */ +const struct phase_vocoder_proc_fnmap phase_vocoder_functions[] = { +#if CONFIG_FORMAT_S16LE + {SOF_IPC_FRAME_S16_LE, phase_vocoder_s16}, +#endif +#if CONFIG_FORMAT_S24LE + {SOF_IPC_FRAME_S24_4LE, phase_vocoder_s24}, +#endif +#if CONFIG_FORMAT_S32LE + {SOF_IPC_FRAME_S32_LE, phase_vocoder_s32}, +#endif +}; + +/** + * phase_vocoder_find_proc_func() - Find suitable processing function. + * @src_fmt: Enum value for PCM format. + * + * This function finds the suitable processing function to use for + * the used PCM format. If not found, return NULL. + * + * Return: Pointer to processing function for the requested PCM format. + */ +phase_vocoder_func phase_vocoder_find_proc_func(enum sof_ipc_frame src_fmt) +{ + int i; + + /* Find suitable processing function from map */ + for (i = 0; i < ARRAY_SIZE(phase_vocoder_functions); i++) + if (src_fmt == phase_vocoder_functions[i].frame_fmt) + return phase_vocoder_functions[i].phase_vocoder_function; + + return NULL; +} diff --git a/src/audio/phase_vocoder/phase_vocoder_setup.c b/src/audio/phase_vocoder/phase_vocoder_setup.c new file mode 100644 index 000000000000..c6baf5b8908a --- /dev/null +++ b/src/audio/phase_vocoder/phase_vocoder_setup.c @@ -0,0 +1,264 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 Intel Corporation. + +#include +#include +#include +#include +#include +#include +#include +#include "phase_vocoder.h" + +#include +#include +#include + +#define STFT_MAX_ALLOC_SIZE 65536 + +LOG_MODULE_REGISTER(phase_vocoder_setup, CONFIG_SOF_LOG_LEVEL); + +static void phase_vocoder_init_buffer(struct phase_vocoder_buffer *buf, int32_t *base, int size) +{ + buf->addr = base; + buf->end_addr = base + size; + buf->r_ptr = base; + buf->w_ptr = base; + buf->s_free = size; + buf->s_avail = 0; + buf->s_length = size; +} + +static int phase_vocoder_get_window(struct phase_vocoder_state *state, + enum sof_phase_vocoder_fft_window_type name) +{ + struct phase_vocoder_fft *fft = &state->fft; + + switch (name) { + case STFT_RECTANGULAR_WINDOW: + win_rectangular_32b(state->window, fft->fft_size); + return 0; + case STFT_BLACKMAN_WINDOW: + win_blackman_32b(state->window, fft->fft_size, WIN_BLACKMAN_A0_Q31); + return 0; + case STFT_HAMMING_WINDOW: + win_hamming_32b(state->window, fft->fft_size); + return 0; + case STFT_HANN_WINDOW: + win_hann_32b(state->window, fft->fft_size); + return 0; + + default: + return -EINVAL; + } +} + +int phase_vocoder_setup(struct processing_module *mod) +{ + struct phase_vocoder_comp_data *cd = module_get_private_data(mod); + struct comp_dev *dev = mod->dev; + struct sof_phase_vocoder_config *config = cd->config; + struct phase_vocoder_state *state = &cd->state; + struct phase_vocoder_fft *fft = &state->fft; + struct phase_vocoder_polar *polar = &state->polar; + size_t sample_buffers_size; + size_t ibuf_size; + size_t obuf_size; + size_t prev_size; + int32_t *addr; + int channels; + int ret; + int i; + + comp_dbg(dev, "phase_vocoder_setup()"); + + /* Check size */ + if (config->size != sizeof(struct sof_phase_vocoder_config)) { + comp_err(dev, "Illegal configuration size %d.", config->size); + return -EINVAL; + } + + if (config->sample_frequency != cd->sample_rate) + comp_warn(dev, "Config sample_frequency does not match stream"); + + if (config->mono) { + cd->process_channels = 1; + if (cd->stream_channels > 1) + cd->mono_mix_coef = (int32_t)((1LL << 31) / cd->stream_channels); + else + cd->mono_mix_coef = INT32_MAX; /* Q1.31 1.0 */ + + comp_info(dev, "mono_mix_coef %d", cd->mono_mix_coef); + } else { + cd->process_channels = cd->stream_channels; + cd->mono_mix_coef = 0; + } + + fft->fft_size = config->frame_length; + fft->fft_hop_size = config->frame_shift; + fft->half_fft_size = (fft->fft_size >> 1) + 1; + + /* FFT size needs to be a multiple of 4 for Xtensa HiFi SIMD, + * and FFT hop size needs to be a multiple of 2. Check also + * for otherwise sane values. + */ + if (fft->fft_size <= 0 || fft->fft_hop_size <= 0 || + fft->fft_hop_size > fft->fft_size || + (fft->fft_size & 3) || (fft->fft_hop_size & 1)) { + comp_err(dev, "FFT size %d or hop size %d are invalid.", + fft->fft_size, fft->fft_hop_size); + return -EINVAL; + } + + comp_info(dev, "fft_size = %d, fft_hop_size = %d, window = %d", fft->fft_size, + fft->fft_hop_size, config->window); + + /* Calculated parameters */ + prev_size = fft->fft_size - fft->fft_hop_size; + ibuf_size = fft->fft_hop_size + cd->max_input_frames; + obuf_size = fft->fft_size + fft->fft_hop_size; + state->prev_data_size = prev_size; + + /* Allocate buffer for input samples, overlap buffer, window */ + channels = cd->process_channels; + sample_buffers_size = + sizeof(int32_t) * (channels * (ibuf_size + obuf_size + prev_size) + fft->fft_size); + + if (sample_buffers_size > STFT_MAX_ALLOC_SIZE) { + comp_err(dev, "Illegal allocation size"); + return -EINVAL; + } + + addr = mod_balloc(mod, sample_buffers_size); + if (!addr) { + comp_err(dev, "Failed buffer allocate"); + ret = -ENOMEM; + goto exit; + } + + memset(addr, 0, sample_buffers_size); + state->buffers = addr; + for (i = 0; i < channels; i++) { + phase_vocoder_init_buffer(&state->ibuf[i], addr, ibuf_size); + addr += ibuf_size; + phase_vocoder_init_buffer(&state->obuf[i], addr, obuf_size); + addr += obuf_size; + state->prev_data[i] = addr; + addr += prev_size; + } + state->window = addr; + + /* Allocate buffers for FFT input and output data */ + fft->fft_buffer_size = fft->fft_size * sizeof(struct icomplex32); + fft->fft_buf = mod_balloc(mod, fft->fft_buffer_size); + if (!fft->fft_buf) { + comp_err(dev, "Failed FFT buffer allocate"); + ret = -ENOMEM; + goto cleanup; + } + + fft->fft_out = mod_balloc(mod, fft->fft_buffer_size); + if (!fft->fft_out) { + comp_err(dev, "Failed FFT output allocate"); + ret = -ENOMEM; + goto cleanup; + } + + /* Setup FFT */ + fft->fft_plan = mod_fft_plan_new(mod, fft->fft_buf, fft->fft_out, fft->fft_size, 32); + if (!fft->fft_plan) { + comp_err(dev, "Failed FFT init"); + ret = -EINVAL; + goto cleanup; + } + + fft->ifft_plan = mod_fft_plan_new(mod, fft->fft_out, fft->fft_buf, fft->fft_size, 32); + if (!fft->ifft_plan) { + comp_err(dev, "Failed IFFT init"); + ret = -EINVAL; + goto cleanup; + } + + /* Setup window */ + ret = phase_vocoder_get_window(state, config->window); + if (ret < 0) { + comp_err(dev, "Failed Window function"); + goto cleanup; + } + + /* Need to compensate the window function gain */ + state->gain_comp = config->window_gain_comp; + + /* Allocate buffers for polar format data for magnitude and phase interpolation */ + state->phase_vocoder_polar_bytes = + channels * fft->half_fft_size * (2 * sizeof(struct ipolar32) + 3 * sizeof(int32_t)); + comp_info(dev, "polar buffers size %zu", state->phase_vocoder_polar_bytes); + addr = mod_balloc(mod, state->phase_vocoder_polar_bytes); + if (!addr) { + comp_err(dev, "Failed polar data buffer allocate"); + ret = -ENOMEM; + goto cleanup; + } + + memset(addr, 0, state->phase_vocoder_polar_bytes); + polar->polar_buffer = addr; + for (i = 0; i < channels; i++) { + polar->polar[i] = (struct ipolar32 *)addr; + addr = (int32_t *)((struct ipolar32 *)addr + fft->half_fft_size); + } + + for (i = 0; i < channels; i++) { + polar->polar_prev[i] = (struct ipolar32 *)addr; + addr = (int32_t *)((struct ipolar32 *)addr + fft->half_fft_size); + } + + for (i = 0; i < channels; i++) { + polar->angle_delta[i] = addr; + addr += fft->half_fft_size; + } + + for (i = 0; i < channels; i++) { + polar->angle_delta_prev[i] = addr; + addr += fft->half_fft_size; + } + + for (i = 0; i < channels; i++) { + polar->output_phase[i] = addr; + addr += fft->half_fft_size; + } + + /* Use FFT buffer as scratch */ + polar->polar_tmp = (struct ipolar32 *)fft->fft_out; + comp_dbg(dev, "phase_vocoder_setup(), done"); + return 0; + +cleanup: + phase_vocoder_free_buffers(mod); +exit: + return ret; +} + +void phase_vocoder_free_buffers(struct processing_module *mod) +{ + struct phase_vocoder_comp_data *cd = module_get_private_data(mod); + struct phase_vocoder_state *state = &cd->state; + struct phase_vocoder_fft *fft = &state->fft; + struct phase_vocoder_polar *polar = &state->polar; + + /* All free helpers tolerate NULL; clear pointers so a subsequent reset + * does not double-free if setup() failed mid-way. + */ + mod_fft_plan_free(mod, fft->ifft_plan); + fft->ifft_plan = NULL; + mod_fft_plan_free(mod, fft->fft_plan); + fft->fft_plan = NULL; + mod_free(mod, fft->fft_buf); + fft->fft_buf = NULL; + mod_free(mod, fft->fft_out); + fft->fft_out = NULL; + mod_free(mod, state->buffers); + state->buffers = NULL; + mod_free(mod, polar->polar_buffer); + polar->polar_buffer = NULL; +} diff --git a/src/audio/phase_vocoder/tune/phase_vocoder_s16.sh b/src/audio/phase_vocoder/tune/phase_vocoder_s16.sh new file mode 100755 index 000000000000..5cfa0ee441a5 --- /dev/null +++ b/src/audio/phase_vocoder/tune/phase_vocoder_s16.sh @@ -0,0 +1,48 @@ +#!/bin/bash +set -euo pipefail + +if [ $# -lt 2 ]; then + echo "Usage: $0 " >&2 + exit 1 +fi + +: "${SOF_WORKSPACE:?SOF_WORKSPACE must be set}" + +CLIP_IN=$1 +WAV_OUT=$2 +TESTBENCH=$SOF_WORKSPACE/sof/tools/testbench/build_testbench/install/bin/sof-testbench4 +TPLG_DIR=$SOF_WORKSPACE/sof/tools/build_tools/topology/topology2/development +TPLG=sof-hda-benchmark-phase_vocoder16.tplg + +RAW_IN=$(mktemp --suffix=_in.raw) +RAW_OUT=$(mktemp --suffix=_out.raw) +CONTROL_SCRIPT_LONG=$(mktemp --suffix=_controls.sh) +trap 'rm -f "$RAW_IN" "$RAW_OUT" "$CONTROL_SCRIPT_LONG"' EXIT + +# Uncomment to run under valgrind: +# VALGRIND=(valgrind --leak-check=full --track-origins=yes) +VALGRIND=() +CONTROL_SCRIPT=(-s "$CONTROL_SCRIPT_LONG") + +# Generated script is consumed by the testbench's -s option, not executed +# directly by a shell. +{ + echo "#!/bin/sh" + echo "amixer -c0 cset name='Analog Playback Phase Vocoder speed' 0.5" + echo "amixer -c0 cset name='Analog Playback Phase Vocoder enable' on" + for s in 0.5 0.6 0.7 0.8 0.9 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 \ + 2.0 1.9 1.8 1.7 1.6 1.5 1.4 1.3 1.2 1.1 1.0; do + echo "sleep 1" + echo "amixer -c0 cset name='Analog Playback Phase Vocoder speed' $s" + done + echo "amixer -c0 cset name='Analog Playback Phase Vocoder enable' off" +} > "$CONTROL_SCRIPT_LONG" + +sox "$CLIP_IN" --encoding signed-integer -L -r 48000 -c 2 -b 16 "$RAW_IN" +"${VALGRIND[@]}" "$TESTBENCH" "${CONTROL_SCRIPT[@]}" \ + -r 48000 -c 2 -b S16_LE -p 1,2 \ + -t "$TPLG_DIR/$TPLG" -i "$RAW_IN" -o "$RAW_OUT" +sox --encoding signed-integer -L -r 48000 -c 2 -b 16 "$RAW_OUT" "$WAV_OUT" + +printf '\n' +file "$WAV_OUT" diff --git a/src/audio/phase_vocoder/tune/phase_vocoder_s32.sh b/src/audio/phase_vocoder/tune/phase_vocoder_s32.sh new file mode 100755 index 000000000000..26dbe89b3e1a --- /dev/null +++ b/src/audio/phase_vocoder/tune/phase_vocoder_s32.sh @@ -0,0 +1,48 @@ +#!/bin/bash +set -euo pipefail + +if [ $# -lt 2 ]; then + echo "Usage: $0 " >&2 + exit 1 +fi + +: "${SOF_WORKSPACE:?SOF_WORKSPACE must be set}" + +CLIP_IN=$1 +WAV_OUT=$2 +TESTBENCH=$SOF_WORKSPACE/sof/tools/testbench/build_testbench/install/bin/sof-testbench4 +TPLG_DIR=$SOF_WORKSPACE/sof/tools/build_tools/topology/topology2/development +TPLG=sof-hda-benchmark-phase_vocoder32.tplg + +RAW_IN=$(mktemp --suffix=_in.raw) +RAW_OUT=$(mktemp --suffix=_out.raw) +CONTROL_SCRIPT_LONG=$(mktemp --suffix=_controls.sh) +trap 'rm -f "$RAW_IN" "$RAW_OUT" "$CONTROL_SCRIPT_LONG"' EXIT + +# Uncomment to run under valgrind: +# VALGRIND=(valgrind --leak-check=full --track-origins=yes) +VALGRIND=() +CONTROL_SCRIPT=(-s "$CONTROL_SCRIPT_LONG") + +# Generated script is consumed by the testbench's -s option, not executed +# directly by a shell. +{ + echo "#!/bin/sh" + echo "amixer -c0 cset name='Analog Playback Phase Vocoder speed' 0.5" + echo "amixer -c0 cset name='Analog Playback Phase Vocoder enable' on" + for s in 0.5 0.6 0.7 0.8 0.9 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 \ + 2.0 1.9 1.8 1.7 1.6 1.5 1.4 1.3 1.2 1.1 1.0; do + echo "sleep 1" + echo "amixer -c0 cset name='Analog Playback Phase Vocoder speed' $s" + done + echo "amixer -c0 cset name='Analog Playback Phase Vocoder enable' off" +} > "$CONTROL_SCRIPT_LONG" + +sox "$CLIP_IN" --encoding signed-integer -L -r 48000 -c 2 -b 32 "$RAW_IN" +"${VALGRIND[@]}" "$TESTBENCH" "${CONTROL_SCRIPT[@]}" \ + -r 48000 -c 2 -b S32_LE -p 1,2 \ + -t "$TPLG_DIR/$TPLG" -i "$RAW_IN" -o "$RAW_OUT" +sox --encoding signed-integer -L -r 48000 -c 2 -b 32 "$RAW_OUT" "$WAV_OUT" + +printf '\n' +file "$WAV_OUT" diff --git a/src/audio/phase_vocoder/tune/setup_phase_vocoder.m b/src/audio/phase_vocoder/tune/setup_phase_vocoder.m new file mode 100644 index 000000000000..0bc2ab4061d8 --- /dev/null +++ b/src/audio/phase_vocoder/tune/setup_phase_vocoder.m @@ -0,0 +1,180 @@ +% setup_phase_vocoder() +% +% Create binary configuration blob for Phase Vocoder component. + +% SPDX-License-Identifier: BSD-3-Clause +% +% Copyright (c) 2026, Intel Corporation. + +function setup_phase_vocoder(cfg) + + cfg.tools_path = '../../../../tools/'; + cfg.tplg_path = [cfg.tools_path 'topology/topology2/include/components/phase_vocoder/']; + cfg.common_path = [cfg.tools_path 'tune/common']; + cfg.tplg_ver = 2; + cfg.ipc_ver = 4; + cfg.mono = 0; + cfg.sample_frequency = 48000; + + cfg.frame_length = 256; % 5.3 ms + cfg.frame_shift = 128; % 2.7 ms + cfg.window_type = 'hann'; + cfg.tplg_fn = 'hann_256_128.conf'; + export_phase_vocoder_setup(cfg); + + cfg.frame_length = 512; % 10.7 ms + cfg.frame_shift = 128; % 2.7 ms + cfg.window_type = 'hann'; + cfg.tplg_fn = 'hann_512_128.conf'; + export_phase_vocoder_setup(cfg); + + cfg.frame_length = 512; % 10.7 ms + cfg.frame_shift = 256; % 5.3 ms + cfg.window_type = 'hann'; + cfg.tplg_fn = 'hann_512_256.conf'; + export_phase_vocoder_setup(cfg); + + % Default configuration, stereo + cfg.frame_length = 1024; % 21.3 ms + cfg.frame_shift = 256; % 5.3 ms + cfg.window_type = 'hann'; + cfg.tplg_fn = 'hann_1024_256.conf'; + export_phase_vocoder_setup(cfg); + + % Default configuration, force mono processing + cfg.mono = 1; + cfg.tplg_fn = 'hann_1024_256_mono.conf'; + export_phase_vocoder_setup(cfg); + +end + +function export_phase_vocoder_setup(cfg) + + % Use blob tool from EQ + addpath(cfg.common_path); + + % Blob size, size plus reserved(8) + current parameters + nbytes_data = 64; + + % Get ABI information + [abi_bytes, nbytes_abi] = sof_get_abi(nbytes_data, cfg.ipc_ver); + + % Initialize correct size uint8 array + nbytes = nbytes_abi + nbytes_data; + b8 = uint8(zeros(1,nbytes)); + + % Insert ABI header + fprintf(1, 'PHASE_VOCODER blob size is %d, ABI header is %d, data is %d\n',nbytes, nbytes_abi, nbytes_data); + b8(1:nbytes_abi) = abi_bytes; + j = nbytes_abi + 1; + + % Apply default PHASE_VOCODER configuration, first struct header and reserved, then data + [b8, j] = add_w32b(nbytes_data, b8, j); + for i = 1:8 + [b8, j] = add_w32b(0, b8, j); + end + + fft_length = cfg.frame_length; + fft_hop = cfg.frame_shift; + [window_idx, window_gain_comp] = get_window(cfg, fft_length, fft_hop); + + v = q_convert(cfg.sample_frequency, 0); [b8, j] = add_w32b(v, b8, j); + v = q_convert(window_gain_comp, 31); [b8, j] = add_w32b(v, b8, j); + v = 0; [b8, j] = add_w32b(v, b8, j); % reserved + v = cfg.mono; [b8, j] = add_w16b(v, b8, j); + v = fft_length; [b8, j] = add_w16b(v, b8, j); + v = fft_hop; [b8, j] = add_w16b(v, b8, j); + v = 0; [b8, j] = add_w16b(v, b8, j); % reserved + v = 0; [b8, j] = add_w32b(v, b8, j); % reserved_pad + v = window_idx; [b8, j] = add_w32b(v, b8, j); % enum window + + % Export + switch cfg.tplg_ver + case 2 + sof_tplg2_write([cfg.tplg_path cfg.tplg_fn], b8, "phase_vocoder_config", ... + "Exported PHASE_VOCODER configuration", ... + "cd src/audio/phase_vocoder/tune; octave setup_phase_vocoder.m"); + otherwise + error("Illegal cfg.tplg_ver, use 2 topology v2."); + end + + rmpath(cfg.common_path); + +end + +%% Helper functions + +function [idx, iwg] = get_window(cfg, len, hop) + switch lower(cfg.window_type) + case 'rectangular' + win = boxcar(len); + idx = 0; + case 'blackman' + win = blackman(len); + idx = 1; + case 'hamming' + win = hamming(len); + idx = 2; + case 'hann' + win = hann(len); + idx = 3; + otherwise + error('Unknown window type'); + end + iwg = hop / sum(win.^2); +end + +function bytes = w8b(word) + bytes = uint8(zeros(1,1)); + bytes(1) = bitand(word, 255); +end + +function bytes = w16b(word) + sh = [0 -8]; + bytes = uint8(zeros(1,2)); + bytes(1) = bitand(bitshift(word, sh(1)), 255); + bytes(2) = bitand(bitshift(word, sh(2)), 255); +end + +function bytes = w32b(word) + sh = [0 -8 -16 -24]; + bytes = uint8(zeros(1,4)); + bytes(1) = bitand(bitshift(word, sh(1)), 255); + bytes(2) = bitand(bitshift(word, sh(2)), 255); + bytes(3) = bitand(bitshift(word, sh(3)), 255); + bytes(4) = bitand(bitshift(word, sh(4)), 255); +end + +function n = q_convert(val, q) + n = round(val * 2^q); +end + +function [blob8, j] = add_w8b(v, blob8, j) + if j > length(blob8) + error('Blob size is not sufficient'); + end + blob8(j) = w8b(v); + j = j + 1; +end + +function [blob8, j] = add_w16b(v, blob8, j) + if j + 1 > length(blob8) + error('Blob size is not sufficient'); + end + if v < 0 + v = 2^16 + v; + end + blob8(j : j + 1) = w16b(v); + j = j + 2; +end + +function [blob8, j] = add_w32b(v, blob8, j) + if j + 3 > length(blob8) + error('Blob size is not sufficient'); + end + if v < 0 + v = 2^32 + v; + end + blob8(j : j + 3) = w32b(v); + j = j + 4; +end diff --git a/src/audio/pipeline/pipeline-graph.c b/src/audio/pipeline/pipeline-graph.c index 47d5d0127fd0..adcb00a80719 100644 --- a/src/audio/pipeline/pipeline-graph.c +++ b/src/audio/pipeline/pipeline-graph.c @@ -7,8 +7,11 @@ #include #include +#include #include #include +#include +#include #include #include #include @@ -103,8 +106,7 @@ static inline void pipeline_posn_offset_put(uint32_t posn_offset) void pipeline_posn_init(struct sof *sof) { - sof->pipeline_posn = platform_shared_get(&pipeline_posn_shared, - sizeof(pipeline_posn_shared)); + sof->pipeline_posn = &pipeline_posn_shared; k_spinlock_init(&sof->pipeline_posn->lock); } @@ -179,25 +181,31 @@ static void buffer_set_comp(struct comp_buffer *buffer, struct comp_dev *comp, } #ifdef CONFIG_SOF_USERSPACE_LL +/* + * User-space LL: callers (IPC handlers) already hold the per-core LL + * lock via user_ll_lock_sched()/ll_block() while modifying pipeline + * connections, which provides mutual exclusion with the LL thread. No + * additional lock is taken here; instead assert that the lock is held. + */ #define PPL_LOCK_DECLARE -#define PPL_LOCK() do { \ - int ret = sys_mutex_lock(&comp->list_mutex, K_FOREVER); \ - assert(ret == 0); \ - } while (0) -#define PPL_UNLOCK() do { \ - int ret = sys_mutex_unlock(&comp->list_mutex); \ - assert(ret == 0); \ - } while (0) +#define PPL_LOCK(x) user_ll_assert_locked(x) +#define PPL_UNLOCK() #else +/* + * Kernel-space LL. When modifying pipeline connections, block IRQs + * and prevent LL from running. No locking needed when iterating + * the pipeline in the LL thread. + */ #define PPL_LOCK_DECLARE uint32_t flags -#define PPL_LOCK() irq_local_disable(flags) -#define PPL_UNLOCK() irq_local_enable(flags) +#define PPL_LOCK(x) irq_local_disable(flags) +#define PPL_UNLOCK() irq_local_enable(flags) #endif int pipeline_connect(struct comp_dev *comp, struct comp_buffer *buffer, int dir) { struct list_item *comp_list; + int ret; PPL_LOCK_DECLARE; if (dir == PPL_CONN_DIR_COMP_TO_BUFFER) @@ -205,10 +213,16 @@ int pipeline_connect(struct comp_dev *comp, struct comp_buffer *buffer, else comp_info(comp, "connect buffer %d as source", buf_get_id(buffer)); - PPL_LOCK(); + PPL_LOCK(buffer->core); comp_list = comp_buffer_list(comp, dir); - buffer_attach(buffer, comp_list, dir); + ret = buffer_attach(buffer, comp_list, dir); + if (ret < 0) { + comp_err(comp, "buffer %d already connected dir %d", + buf_get_id(buffer), dir); + PPL_UNLOCK(); + return ret; + } buffer_set_comp(buffer, comp, dir); PPL_UNLOCK(); @@ -226,7 +240,7 @@ void pipeline_disconnect(struct comp_dev *comp, struct comp_buffer *buffer, int else comp_dbg(comp, "disconnect buffer %d as source", buf_get_id(buffer)); - PPL_LOCK(); + PPL_LOCK(buffer->core); comp_list = comp_buffer_list(comp, dir); buffer_detach(buffer, comp_list, dir); @@ -287,8 +301,14 @@ static int pipeline_comp_complete(struct comp_dev *current, * It will be calculated during module prepare operation * either by the module or to default value based on module's OBS */ - if (current->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_LL) + if (current->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_LL) { current->period = ppl_data->p->period; + } else { + struct processing_module *mod = comp_mod(current); + + if (mod && mod->priv.resources.alloc) + vregion_set_interim(mod->priv.resources.alloc->vreg); + } current->priority = ppl_data->p->priority; @@ -362,7 +382,7 @@ static int pipeline_comp_reset(struct comp_dev *current, * scheduled together, except for IPC4, where each pipeline receives * commands from the host separately */ - if (!is_single_ppl && IPC4_MOD_ID(current->ipc_config.id)) + if (!is_single_ppl && IS_ENABLED(CONFIG_IPC_MAJOR_4)) return 0; /* Propagate reset across pipelines only in the same direction diff --git a/src/audio/pipeline/pipeline-params.c b/src/audio/pipeline/pipeline-params.c index 16273e0897d4..ed32fc9e3b02 100644 --- a/src/audio/pipeline/pipeline-params.c +++ b/src/audio/pipeline/pipeline-params.c @@ -267,7 +267,7 @@ static int pipeline_comp_prepare(struct comp_dev *current, if (!comp_is_single_pipeline(current, ppl_data->start)) { /* ipc4 module is only prepared in its parent pipeline */ - if (IPC4_MOD_ID(current->ipc_config.id)) + if (IS_ENABLED(CONFIG_IPC_MAJOR_4)) return 0; /* Propagate prepare only to pipelines in the same direction */ diff --git a/src/audio/pipeline/pipeline-schedule.c b/src/audio/pipeline/pipeline-schedule.c index 45fd1eed639c..eee3b2c406d7 100644 --- a/src/audio/pipeline/pipeline-schedule.c +++ b/src/audio/pipeline/pipeline-schedule.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -140,7 +141,7 @@ static enum task_state pipeline_task_cmd(struct pipeline *p, err = SOF_TASK_STATE_RESCHEDULE; } else if (p->status == COMP_STATE_PAUSED) { /* reset the pipeline components for IPC4 after the STOP trigger */ - if (cmd == COMP_TRIGGER_STOP && IPC4_MOD_ID(host->ipc_config.id)) { + if (cmd == COMP_TRIGGER_STOP && IS_ENABLED(CONFIG_IPC_MAJOR_4)) { err = pipeline_reset(host->pipeline, host); if (err < 0) reply->error = err; @@ -282,14 +283,31 @@ void pipeline_schedule_triggered(struct pipeline_walk_context *ctx, struct pipeline_data *ppl_data = ctx->comp_data; struct list_item *tlist; struct pipeline *p; - uint32_t flags; +#ifdef CONFIG_IPC_MAJOR_4 /* - * Interrupts have to be disabled while adding tasks to or removing them + * With IPC4, each pipeline is triggered separately. Exactly 1 pipeline + * is expected in the pipelines list (it's unclear whether an empty list + * should be tolerated). + */ + assert(list_is_empty(&ctx->pipelines) || + list_item_is_last(ctx->pipelines.next, &ctx->pipelines)); +#endif + + /* + * Block scheduler execution when adding tasks or removing them * from the scheduler list. Without that scheduling can begin * immediately before all pipelines achieved a consistent state. */ +#ifdef CONFIG_SOF_USERSPACE_LL + int sched_core = ppl_data->start->ipc_config.core; + + user_ll_lock_sched(sched_core); +#else + uint32_t flags; + irq_local_disable(flags); +#endif switch (cmd) { case COMP_TRIGGER_PAUSE: @@ -345,8 +363,11 @@ void pipeline_schedule_triggered(struct pipeline_walk_context *ctx, p->xrun_bytes = 1; } } - +#ifdef CONFIG_SOF_USERSPACE_LL + user_ll_unlock_sched(sched_core); +#else irq_local_enable(flags); +#endif } int pipeline_comp_ll_task_init(struct pipeline *p) diff --git a/src/audio/pipeline/pipeline-stream.c b/src/audio/pipeline/pipeline-stream.c index 8bfdfc182912..5fd795e818db 100644 --- a/src/audio/pipeline/pipeline-stream.c +++ b/src/audio/pipeline/pipeline-stream.c @@ -21,6 +21,7 @@ #include #include #include +#include #ifdef CONFIG_IPC_MAJOR_4 #include @@ -145,6 +146,20 @@ static int pipeline_comp_copy(struct comp_dev *current, return err; } +#ifdef CONFIG_SOF_USERSPACE_LL +/* + * User-space LL: pipeline_copy() runs as an LL task, with the per-core + * LL lock already held by the LL thread for the whole tick. Taking the + * lock again here would only add syscall overhead on this hot path, so + * we just assert that the lock is in fact held. + */ +#define PPL_LOCK(x) user_ll_assert_locked(x) +#define PPL_UNLOCK() +#else +#define PPL_LOCK(x) +#define PPL_UNLOCK() +#endif + /* Copy data across all pipeline components. * For capture pipelines it always starts from source component * and continues downstream and for playback pipelines it first @@ -162,6 +177,8 @@ int pipeline_copy(struct pipeline *p) uint32_t dir; int ret; + PPL_LOCK(p->core); + if (p->source_comp->direction == SOF_IPC_STREAM_PLAYBACK) { dir = PPL_DIR_UPSTREAM; start = p->sink_comp; @@ -178,6 +195,8 @@ int pipeline_copy(struct pipeline *p) pipe_err(p, "ret = %d, start->comp.id = %u, dir = %u", ret, dev_comp_id(start), dir); + PPL_UNLOCK(); + return ret; } @@ -229,7 +248,7 @@ static int pipeline_comp_list(struct comp_dev *current, * component and we aren't using IPC4. With IPC4 each pipeline receives * commands separately so we don't need to trigger them together */ - if (!is_single_ppl && (!is_same_sched || IPC4_MOD_ID(current->ipc_config.id))) { + if (!is_single_ppl && (!is_same_sched || IS_ENABLED(CONFIG_IPC_MAJOR_4))) { pipe_dbg(current->pipeline, "current is from another pipeline"); return 0; diff --git a/src/audio/rtnr/rtnr.c b/src/audio/rtnr/rtnr.c index 45ff62a60f8d..13c4f8d12b4d 100644 --- a/src/audio/rtnr/rtnr.c +++ b/src/audio/rtnr/rtnr.c @@ -454,6 +454,11 @@ static int rtnr_get_config(struct processing_module *mod, return rtnr_get_bin_data(mod, cdata, fragment_size); case SOF_CTRL_CMD_SWITCH: + if (cdata->num_elems > SOF_IPC_MAX_CHANNELS) { + comp_err(dev, "num_elems %u > max %u", + cdata->num_elems, SOF_IPC_MAX_CHANNELS); + return -EINVAL; + } for (j = 0; j < cdata->num_elems; j++) { cdata->chanv[j].channel = j; cdata->chanv[j].value = cd->process_enable; @@ -560,6 +565,12 @@ static int32_t rtnr_set_value(struct processing_module *mod, void *ctl_data) uint32_t val = 0; int32_t j; + if (cdata->num_elems > SOF_IPC_MAX_CHANNELS) { + comp_err(dev, "num_elems %u > max %u", + cdata->num_elems, SOF_IPC_MAX_CHANNELS); + return -EINVAL; + } + for (j = 0; j < cdata->num_elems; j++) { val |= cdata->chanv[j].value; comp_dbg(dev, "value = %u", val); diff --git a/src/audio/selector/selector.c b/src/audio/selector/selector.c index 6afc92d8bdfc..faf21da0e228 100644 --- a/src/audio/selector/selector.c +++ b/src/audio/selector/selector.c @@ -231,16 +231,86 @@ static int selector_ctrl_set_data(struct comp_dev *dev, struct sof_ipc_ctrl_data *cdata) { struct comp_data *cd = comp_get_drvdata(dev); + struct comp_buffer *src; struct sof_sel_config *cfg; + uint32_t src_channels; int ret = 0; switch (cdata->cmd) { case SOF_CTRL_CMD_BINARY: comp_dbg(dev, "SOF_CTRL_CMD_BINARY"); + if (cdata->data->size < sizeof(struct sof_sel_config)) { + comp_err(dev, "invalid config blob size %u", cdata->data->size); + return -EINVAL; + } + cfg = (struct sof_sel_config *) ASSUME_ALIGNED(&cdata->data->data, 4); + /* + * The config validated at .params() time can be replaced here at + * runtime, so re-validate the new channel counts and selected + * channel before accepting them; otherwise an out-of-range value + * later indexes past the source channels in the copy routine. + */ + switch (cfg->in_channels_count) { + case 0: + case SEL_SOURCE_1CH: + case SEL_SOURCE_2CH: + case SEL_SOURCE_4CH: + break; + default: + comp_err(dev, "invalid in_channels_count %u", + cfg->in_channels_count); + return -EINVAL; + } + + switch (cfg->out_channels_count) { + case 0: + case SEL_SINK_1CH: + case SEL_SINK_2CH: + case SEL_SINK_4CH: + break; + default: + comp_err(dev, "invalid out_channels_count %u", + cfg->out_channels_count); + return -EINVAL; + } + + /* sel_channel indexes the source channels, so it must be below + * the source channel count, otherwise the copy routine reads + * past the end of each source frame. The copy routine strides by + * the live producer stream channel count, so when the source is + * already connected that live count is the authority for both the + * selected channel and any fixed input count; before connection + * fall back to the configured input count. Always cap by the + * maximum supported source width. + */ + src = comp_dev_get_first_data_producer(dev); + if (src) + src_channels = audio_stream_get_channels(&src->stream); + else + src_channels = cfg->in_channels_count; + + /* A fixed (non-zero) input count must not exceed the live source + * width, otherwise sel_channel could be accepted below the + * requested count yet still index past the actual stream. + */ + if (src && cfg->in_channels_count && + cfg->in_channels_count > src_channels) { + comp_err(dev, "in_channels_count %u exceeds source channels %u", + cfg->in_channels_count, src_channels); + return -EINVAL; + } + + if (cfg->sel_channel >= SEL_SOURCE_4CH || + (src_channels && cfg->sel_channel >= src_channels)) { + comp_err(dev, "invalid sel_channel %u (source channels %u)", + cfg->sel_channel, src_channels); + return -EINVAL; + } + /* Just set the configuration */ cd->config.in_channels_count = cfg->in_channels_count; cd->config.out_channels_count = cfg->out_channels_count; @@ -539,8 +609,7 @@ static SHARED_DATA struct comp_driver_info comp_selector_info = { /** \brief Initializes selector component. */ UT_STATIC void sys_comp_selector_init(void) { - comp_register(platform_shared_get(&comp_selector_info, - sizeof(comp_selector_info))); + comp_register(&comp_selector_info); } DECLARE_MODULE(sys_comp_selector_init); diff --git a/src/audio/selector/tune/sof_selector_blobs.m b/src/audio/selector/tune/sof_selector_blobs.m index 1b370d4030de..b2f70069ee0b 100644 --- a/src/audio/selector/tune/sof_selector_blobs.m +++ b/src/audio/selector/tune/sof_selector_blobs.m @@ -23,6 +23,7 @@ function sof_selector_blobs() IPC4_CHANNEL_CONFIG_STEREO = 1; IPC4_CHANNEL_CONFIG_QUATRO = 5; IPC4_CHANNEL_CONFIG_5_POINT_1 = 8; + IPC4_CHANNEL_CONFIG_DUAL_MONO = 9; IPC4_CHANNEL_CONFIG_7_POINT_1 = 12; % Matrix for 1:1 pass-through @@ -39,6 +40,16 @@ function sof_selector_blobs() sel.coeffs(1, 2) = 0.7071; stereo_to_mono_pack8 = write_blob(sel, "downmix_stereo_to_mono"); + % Stereo to dual-mono downmix + sel.ch_count = [2 2]; + sel.ch_config = [IPC4_CHANNEL_CONFIG_STEREO IPC4_CHANNEL_CONFIG_DUAL_MONO]; + sel.coeffs = zeros(8,8); + sel.coeffs(1, 1) = 0.5; + sel.coeffs(1, 2) = 0.5; + sel.coeffs(2, 1) = 0.5; + sel.coeffs(2, 2) = 0.5; + stereo_to_doublemono_pack8 = write_blob(sel, "downmix_stereo_to_doublemono"); + % 5.1 to stereo downmix sel.ch_count = [6 2]; sel.ch_config = [IPC4_CHANNEL_CONFIG_5_POINT_1 IPC4_CHANNEL_CONFIG_STEREO]; @@ -189,7 +200,7 @@ function write_8bit_packed(pack8, blobname) blob8 = sof_selector_build_blob(pack8); str_config = "selector_config"; str_exported = "Exported with script sof_selector_blobs.m"; - str_howto = "cd tools/tune/selector; octave sof_selector_blobs.m"; + str_howto = "cd src/audio/selector/tune; octave sof_selector_blobs.m"; sof_tools = '../../../../tools'; sof_tplg = fullfile(sof_tools, 'topology'); sof_tplg_selector = fullfile(sof_tplg, 'topology2/include/components/micsel'); diff --git a/src/audio/smart_amp/smart_amp.c b/src/audio/smart_amp/smart_amp.c index f82e3d2f7360..38577545b36e 100644 --- a/src/audio/smart_amp/smart_amp.c +++ b/src/audio/smart_amp/smart_amp.c @@ -289,7 +289,11 @@ static int smart_amp_get_config(struct processing_module *mod, comp_dbg(dev, "actual blob size = %zu, expected blob size = %zu", bs, sizeof(struct sof_smart_amp_config)); - if (bs == 0 || bs > size) + /* bs is the host-set config.size and is used as the memcpy source + * length from the fixed-size sad->config, so bound it by the struct + * size. memcpy_s() below already bounds it against the destination. + */ + if (bs == 0 || bs > sizeof(struct sof_smart_amp_config)) return -EINVAL; ret = memcpy_s(cdata->data->data, size, &sad->config, bs); @@ -521,8 +525,15 @@ static int smart_amp_ff_process(struct processing_module *mod, return 0; } - if (frames > SMART_AMP_FF_BUF_DB_SZ) { - comp_err(dev, "feed forward frame size overflow: %u", frames); + /* + * The remap functions write frames * ff_mod.channels samples into + * ff_mod.buf, which holds SMART_AMP_FF_BUF_DB_SZ samples. Bound the + * total sample count, not just the frame count, so an unexpected + * channel count cannot overflow the buffer. + */ + if ((uint64_t)frames * sad->ff_mod.channels > SMART_AMP_FF_BUF_DB_SZ) { + comp_err(dev, "feed forward frame size overflow: %u frames, %u ch", + frames, sad->ff_mod.channels); sad->ff_mod.consumed = frames; return -EINVAL; } @@ -556,8 +567,10 @@ static int smart_amp_fb_process(struct processing_module *mod, return 0; } - if (frames > SMART_AMP_FB_BUF_DB_SZ) { - comp_err(dev, "feedback frame size overflow: %u", frames); + /* bound total samples (frames * channels) against the buffer size */ + if ((uint64_t)frames * sad->fb_mod.channels > SMART_AMP_FB_BUF_DB_SZ) { + comp_err(dev, "feedback frame size overflow: %u frames, %u ch", + frames, sad->fb_mod.channels); sad->fb_mod.consumed = frames; return -EINVAL; } diff --git a/src/audio/smart_amp/smart_amp_generic.c b/src/audio/smart_amp/smart_amp_generic.c index 91223eb398bd..72f81d0e5117 100644 --- a/src/audio/smart_amp/smart_amp_generic.c +++ b/src/audio/smart_amp/smart_amp_generic.c @@ -35,7 +35,11 @@ static void remap_s32_to_s32(struct smart_amp_mod_stream *src_mod, uint32_t fram n = MIN(num_samples_remaining, nmax); for (ch = 0; ch < src_mod->channels; ch++) { - if (chan_map[ch] == -1) + /* skip unmapped (-1) and out-of-range source channels; + * the uint8_t cast folds the negative case into the + * single upper-bound check (-1 becomes 255 >= src_ch) + */ + if ((uint8_t)chan_map[ch] >= src_ch) continue; mod_ptr = mod_ptr_base + ch; @@ -103,7 +107,11 @@ static void remap_s16_to_s16(struct smart_amp_mod_stream *src_mod, uint32_t fram n = MIN(num_samples_remaining, nmax); for (ch = 0; ch < src_mod->channels; ch++) { - if (chan_map[ch] == -1) + /* skip unmapped (-1) and out-of-range source channels; + * the uint8_t cast folds the negative case into the + * single upper-bound check (-1 becomes 255 >= src_ch) + */ + if ((uint8_t)chan_map[ch] >= src_ch) continue; mod_ptr = mod_ptr_base + ch; @@ -147,7 +155,11 @@ static void remap_s16_to_b32(struct smart_amp_mod_stream *src_mod, uint32_t fram n = MIN(num_samples_remaining, nmax); for (ch = 0; ch < src_mod->channels; ch++) { - if (chan_map[ch] == -1) + /* skip unmapped (-1) and out-of-range source channels; + * the uint8_t cast folds the negative case into the + * single upper-bound check (-1 becomes 255 >= src_ch) + */ + if ((uint8_t)chan_map[ch] >= src_ch) continue; mod_ptr = mod_ptr_base + ch; diff --git a/src/audio/smart_amp/smart_amp_maxim_dsm.c b/src/audio/smart_amp/smart_amp_maxim_dsm.c index b9289861aa8d..4b808dfaaf36 100644 --- a/src/audio/smart_amp/smart_amp_maxim_dsm.c +++ b/src/audio/smart_amp/smart_amp_maxim_dsm.c @@ -321,7 +321,18 @@ static int maxim_dsm_get_param(struct smart_amp_mod_struct_t *hspk, * required size */ if (bs > size) { - comp_err(dev, "[DSM] invalid size %d", bs); + comp_err(dev, "[DSM] invalid size %zu", bs); + return -EINVAL; + } + + /* Host controls msg_index and therefore data_pos; make sure the + * fragment stays inside caldata->data to avoid leaking adjacent + * heap back to the host. + */ + if (caldata->data_pos >= caldata->data_size || + bs > caldata->data_size - caldata->data_pos) { + comp_err(dev, "[DSM] invalid data_pos %u, size %zu, total %u", + caldata->data_pos, bs, caldata->data_size); return -EINVAL; } @@ -385,6 +396,13 @@ static int maxim_dsm_set_param(struct smart_amp_mod_struct_t *hspk, id = DSM_CH_MASK(param->param.id); ch = (param->param.id & DSM_CH1_BITMASK) ? 0 : 1; + /* id indexes the model database; reject values past its size */ + if (id >= hspk->param.max_param) { + comp_err(dev, "[DSM] invalid param id:%x max:%d", + id, hspk->param.max_param); + return -EINVAL; + } + /* 2nd channel has (hspk->param.max_param * DSM_PARAM_MAX) sized offset */ db[(id + ch * hspk->param.max_param) * DSM_PARAM_MAX + DSM_PARAM_VALUE] = param->param.value; diff --git a/src/audio/tdfb/tdfb.c b/src/audio/tdfb/tdfb.c index 5df4563c3558..bf3d1dd8ff5e 100644 --- a/src/audio/tdfb/tdfb.c +++ b/src/audio/tdfb/tdfb.c @@ -302,73 +302,185 @@ static int wrap_180(int a) return a; } -static int tdfb_init_coef(struct processing_module *mod, int source_nch, - int sink_nch) +/* Walk the TDFB blob and verify that every FIR section and trailing array + * fits exactly inside the IPC payload. The walk mirrors tdfb_init_coef() + * but stays bounded so a malformed blob cannot push tdfb_filter_seek() + * past the buffer end at setup time. The channel-vs-stream relationships + * are also checked here so a mismatching blob cannot replace the working + * configuration during streaming. + */ +static int tdfb_validate_config(struct comp_dev *dev, + struct sof_tdfb_config *config, + size_t config_size) { + struct processing_module *mod = comp_mod(dev); struct tdfb_comp_data *cd = module_get_private_data(mod); struct sof_fir_coef_data *coef_data; - struct sof_tdfb_config *config = cd->config; - struct comp_dev *dev = mod->dev; - int16_t *output_channel_mix_beam_off = NULL; - int16_t *coefp; - int size_sum = 0; - int min_delta_idx; /* Index to beam angle with smallest delta vs. target */ - int min_delta; /* Smallest angle difference found in degrees */ - int max_ch; - int num_filters; - int target_az; /* Target azimuth angle in degrees */ - int delta; /* Target minus found angle in degrees absolute value */ - int idx; - int s; + struct sof_tdfb_angle *filter_angles; + int16_t *input_channel_select; + uint8_t *p; + uint8_t *end; + size_t remaining; + size_t step; + size_t expected; + int16_t max_ch; + int total_filters; int i; - /* Sanity checks */ - if (config->num_output_channels > PLATFORM_MAX_CHANNELS || - !config->num_output_channels) { - comp_err(dev, "invalid num_output_channels %d", + if ((size_t)config->size != config_size) { + comp_err(dev, "blob size %zu / header size %u mismatch", + config_size, config->size); + return -EINVAL; + } + if (!config->num_output_channels || + config->num_output_channels > PLATFORM_MAX_CHANNELS) { + comp_err(dev, "invalid num_output_channels %u", config->num_output_channels); return -EINVAL; } - - if (config->num_output_channels != sink_nch) { - comp_err(dev, "stream output channels count %d does not match configuration %d", - sink_nch, config->num_output_channels); + if (!config->num_filters || config->num_filters > SOF_TDFB_FIR_MAX_COUNT) { + comp_err(dev, "invalid num_filters %u", config->num_filters); return -EINVAL; } - - if (config->num_filters > SOF_TDFB_FIR_MAX_COUNT) { - comp_err(dev, "invalid num_filters %d", - config->num_filters); + if (!config->num_angles || config->num_angles > SOF_TDFB_MAX_ANGLES) { + comp_err(dev, "invalid num_angles %u", config->num_angles); return -EINVAL; } - - if (config->num_angles > SOF_TDFB_MAX_ANGLES) { - comp_err(dev, "invalid num_angles %d", - config->num_angles); + if (!config->angle_enum_mult) { + comp_err(dev, "invalid angle_enum_mult"); return -EINVAL; } - if (config->beam_off_defined > 1) { - comp_err(dev, "invalid beam_off_defined %d", + comp_err(dev, "invalid beam_off_defined %u", config->beam_off_defined); return -EINVAL; } - if (config->num_mic_locations > SOF_TDFB_MAX_MICROPHONES) { - comp_err(dev, "invalid num_mic_locations %d", + comp_err(dev, "invalid num_mic_locations %u", config->num_mic_locations); return -EINVAL; } - /* In SOF v1.6 - 1.8 based beamformer topologies the multiple angles, mic locations, - * and beam on/off switch were not defined. Return error if such configuration is seen. - * A most basic blob has num_angles equals 1. Mic locations data is optional. + total_filters = config->num_filters * (config->num_angles + config->beam_off_defined); + p = (uint8_t *)&config->data[0]; + end = (uint8_t *)config + config_size; + for (i = 0; i < total_filters; i++) { + remaining = end - p; + if (remaining < sizeof(struct sof_fir_coef_data)) { + comp_err(dev, "FIR %d header out of bounds", i); + return -EINVAL; + } + coef_data = (struct sof_fir_coef_data *)p; + if (fir_delay_size(coef_data) <= 0) { + comp_err(dev, "FIR %d invalid length %d", + i, coef_data->length); + return -EINVAL; + } + step = sizeof(struct sof_fir_coef_data) + + (size_t)coef_data->length * sizeof(int16_t); + if (step > remaining) { + comp_err(dev, "FIR %d coefs out of bounds", i); + return -EINVAL; + } + p += step; + } + + /* p now points at input_channel_select[]. The remaining bytes must + * hold exactly: 3 per-filter int16 arrays (input_channel_select, + * output_channel_mix, output_stream_mix), an optional beam-off output + * channel mix, num_angles angle entries and num_mic_locations + * microphone entries. + */ + input_channel_select = (int16_t *)p; + expected = ((size_t)config->num_filters * SOF_TDFB_CONFIG_FILTER_CONTROL_NUM_WORDS + + (size_t)config->beam_off_defined * config->num_filters) * + sizeof(int16_t) + + (size_t)config->num_angles * sizeof(struct sof_tdfb_angle) + + (size_t)config->num_mic_locations * + sizeof(struct sof_tdfb_mic_location); + if ((size_t)(end - p) != expected) { + comp_err(dev, "blob trailer size mismatch: have %zu, expected %zu", + (size_t)(end - p), expected); + return -EINVAL; + } + + /* Each filter_angles[].filter_index points at the first filter of a + * num_filters-wide bank, so it must be in [0, total_filters - + * num_filters]. Validating here ensures the runtime path in + * tdfb_init_coef() cannot be reached with an out-of-range index. */ - if (config->num_angles == 0 && config->num_mic_locations == 0) { - comp_err(dev, "ABI version less than 3.19.1 is not supported."); + filter_angles = (struct sof_tdfb_angle *) + (p + ((size_t)config->num_filters * SOF_TDFB_CONFIG_FILTER_CONTROL_NUM_WORDS + + (size_t)config->beam_off_defined * config->num_filters) * sizeof(int16_t)); + for (i = 0; i < config->num_angles; i++) { + if (filter_angles[i].filter_index < 0 || + filter_angles[i].filter_index + config->num_filters > total_filters) { + comp_err(dev, "invalid filter_index %d for angle %d", + filter_angles[i].filter_index, i); + return -EINVAL; + } + } + + /* The blob must match the running stream. Skip these checks when no + * stream is bound yet (cached channel counts are zero before prepare). + */ + if (cd->sink_channels && config->num_output_channels != cd->sink_channels) { + comp_err(dev, "blob num_output_channels %u does not match sink %d", + config->num_output_channels, cd->sink_channels); + return -EINVAL; + } + if (cd->source_channels) { + max_ch = 0; + for (i = 0; i < config->num_filters; i++) { + if (input_channel_select[i] < 0) { + comp_err(dev, "invalid channel select for filter %d", i); + return -EINVAL; + } + if (input_channel_select[i] > max_ch) + max_ch = input_channel_select[i]; + } + if (max_ch + 1 > cd->source_channels) { + comp_err(dev, "blob needs %d source channels, stream has %d", + max_ch + 1, cd->source_channels); + return -EINVAL; + } + } + + return 0; +} + +static int tdfb_validator(struct comp_dev *dev, void *new_data, uint32_t new_data_size) +{ + if (new_data_size < sizeof(struct sof_tdfb_config) || + new_data_size > SOF_TDFB_MAX_SIZE) { + comp_err(dev, "invalid configuration blob, size %u", new_data_size); return -EINVAL; } + return tdfb_validate_config(dev, new_data, new_data_size); +} + +static int tdfb_init_coef(struct processing_module *mod) +{ + struct tdfb_comp_data *cd = module_get_private_data(mod); + struct sof_fir_coef_data *coef_data; + struct sof_tdfb_config *config = cd->config; + struct comp_dev *dev = mod->dev; + int16_t *output_channel_mix_beam_off = NULL; + int16_t *coefp; + int size_sum = 0; + int min_delta_idx; /* Index to beam angle with smallest delta vs. target */ + int min_delta; /* Smallest angle difference found in degrees */ + int num_filters; + int target_az; /* Target azimuth angle in degrees */ + int delta; /* Target minus found angle in degrees absolute value */ + int idx; + int i; + + /* Blob layout, bounds, size and stream channel relationships are + * pre-validated by tdfb_validator() / tdfb_validate_config(). + */ + /* Skip filter coefficients */ num_filters = config->num_filters * (config->num_angles + config->beam_off_defined); coefp = tdfb_filter_seek(config, num_filters); @@ -381,8 +493,8 @@ static int tdfb_init_coef(struct processing_module *mod, int source_nch, cd->output_stream_mix = coefp; coefp += config->num_filters; - /* Check if there's beam-off configured, then get pointers to beam angles data - * and microphone locations. Finally check that size matches. + /* Check if there's beam-off configured, then get pointers to beam angles + * data and microphone locations. */ if (config->beam_off_defined) { output_channel_mix_beam_off = coefp; @@ -391,11 +503,6 @@ static int tdfb_init_coef(struct processing_module *mod, int source_nch, cd->filter_angles = (struct sof_tdfb_angle *)coefp; cd->mic_locations = (struct sof_tdfb_mic_location *) (&cd->filter_angles[config->num_angles]); - if ((uint8_t *)&cd->mic_locations[config->num_mic_locations] != - (uint8_t *)config + config->size) { - comp_err(dev, "invalid config size"); - return -EINVAL; - } /* Skip to requested coefficient set */ min_delta = 360; @@ -423,45 +530,25 @@ static int tdfb_init_coef(struct processing_module *mod, int source_nch, cd->filter_angles[min_delta_idx].azimuth, idx); } + if (idx < 0 || idx + (int)config->num_filters > num_filters) { + comp_err(dev, "invalid filter_index %d for angle %d", + idx, cd->filter_angles[min_delta_idx].azimuth); + return -EINVAL; + } + /* Seek to proper filter for requested angle or beam off configuration */ coefp = tdfb_filter_seek(config, idx); - /* Initialize filter bank */ + /* Initialize filter bank. FIR header bounds and length validity were + * already checked when the blob entered the component. + */ for (i = 0; i < config->num_filters; i++) { - /* Get delay line size */ coef_data = (struct sof_fir_coef_data *)coefp; - s = fir_delay_size(coef_data); - if (s > 0) { - size_sum += s; - } else { - comp_err(dev, "FIR length %d is invalid", - coef_data->length); - return -EINVAL; - } - - /* Initialize coefficients for FIR filter and find next - * filter. - */ + size_sum += fir_delay_size(coef_data); fir_init_coef(&cd->fir[i], coef_data); coefp = coef_data->coef + coef_data->length; } - /* Find max used input channel */ - max_ch = 0; - for (i = 0; i < config->num_filters; i++) { - if (cd->input_channel_select[i] > max_ch) - max_ch = cd->input_channel_select[i]; - } - - /* The stream must contain at least the number of channels that is - * used for filters input. - */ - if (max_ch + 1 > source_nch) { - comp_err(dev, "stream input channels count %d is not sufficient for configuration %d", - source_nch, max_ch + 1); - return -EINVAL; - } - return size_sum; } @@ -497,7 +584,7 @@ static int tdfb_setup(struct processing_module *mod, int source_nch, int sink_nc } /* Set coefficients for each channel from coefficient blob */ - delay_size = tdfb_init_coef(mod, source_nch, sink_nch); + delay_size = tdfb_init_coef(mod); if (delay_size < 0) return delay_size; /* Contains error code */ @@ -570,6 +657,13 @@ static int tdfb_init(struct processing_module *mod) goto err; } + /* Reject malformed blobs at IPC time so a bad run-time update cannot + * replace the working configuration. The channel-count checks inside + * the validator are skipped until tdfb_prepare() caches the stream + * channel counts. + */ + comp_data_blob_set_validator(cd->model_handler, tdfb_validator); + for (i = 0; i < PLATFORM_MAX_CHANNELS; i++) fir_reset(&cd->fir[i]); @@ -639,9 +733,14 @@ static int tdfb_process(struct processing_module *mod, comp_dbg(dev, "entry"); - /* Check for changed configuration */ + /* Check for changed configuration. The IPC-time validator installed + * in tdfb_init() has already structurally validated the blob, so + * only NULL needs to be guarded here. + */ if (comp_is_new_data_blob_available(cd->model_handler)) { - cd->config = comp_get_data_blob(cd->model_handler, NULL, NULL); + cd->config = comp_get_data_blob(cd->model_handler, &cd->config_size, NULL); + if (!cd->config) + return -EINVAL; ret = tdfb_setup(mod, audio_stream_get_channels(source), audio_stream_get_channels(sink), audio_stream_get_frm_fmt(source)); @@ -705,7 +804,6 @@ static int tdfb_prepare(struct processing_module *mod, struct comp_buffer *sourceb, *sinkb; struct comp_dev *dev = mod->dev; enum sof_ipc_frame frame_fmt; - size_t data_size; int source_channels; int sink_channels; int rate; @@ -734,14 +832,31 @@ static int tdfb_prepare(struct processing_module *mod, sink_channels = audio_stream_get_channels(&sinkb->stream); rate = audio_stream_get_rate(&sourceb->stream); + /* Cache stream channel counts for the blob validator before any + * validate_config() call runs. + */ + cd->source_channels = source_channels; + cd->sink_channels = sink_channels; + /* Initialize filter */ - cd->config = comp_get_data_blob(cd->model_handler, &data_size, NULL); - if (!cd->config || !data_size) { - comp_err(dev, "Missing a configuration blob."); + cd->config = comp_get_data_blob(cd->model_handler, &cd->config_size, NULL); + if (!cd->config) { ret = -EINVAL; goto out; } + /* Re-validate now that the stream channel counts are cached: the + * IPC-time validator skips the channel checks before prepare, so the + * blob must be walked again here before tdfb_setup() uses it. + * Discard a malformed blob so the runtime path (tdfb_process()) + * cannot dereference it. + */ + ret = tdfb_validator(dev, cd->config, (uint32_t)cd->config_size); + if (ret < 0) { + cd->config = NULL; + goto out; + } + ret = tdfb_setup(mod, source_channels, sink_channels, frame_fmt); if (ret < 0) { comp_err(dev, "error: tdfb_setup failed."); @@ -764,18 +879,18 @@ static int tdfb_prepare(struct processing_module *mod, /* Initialize tracking */ ret = tdfb_direction_init(mod, rate, source_channels); - if (!ret) { - comp_info(dev, "max_lag = %d, xcorr_size = %zu", - cd->direction.max_lag, cd->direction.d_size); - comp_info(dev, "line_array = %d, a_step = %d, a_offs = %d", - (int)cd->direction.line_array, cd->config->angle_enum_mult, - cd->config->angle_enum_offs); - } - -out: if (ret < 0) - comp_set_state(dev, COMP_TRIGGER_RESET); + goto out; + comp_info(dev, "max_lag = %d, xcorr_size = %zu", + cd->direction.max_lag, cd->direction.d_size); + comp_info(dev, "line_array = %d, a_step = %d, a_offs = %d", + (int)cd->direction.line_array, cd->config->angle_enum_mult, + cd->config->angle_enum_offs); + + return 0; +out: + comp_set_state(dev, COMP_TRIGGER_RESET); return ret; } @@ -786,6 +901,9 @@ static int tdfb_reset(struct processing_module *mod) comp_dbg(mod->dev, "entry"); + cd->source_channels = 0; + cd->sink_channels = 0; + tdfb_free_delaylines(mod); cd->tdfb_func = NULL; diff --git a/src/audio/tdfb/tdfb.h b/src/audio/tdfb/tdfb.h index d5a09776301f..1787fef919e6 100644 --- a/src/audio/tdfb/tdfb.h +++ b/src/audio/tdfb/tdfb.h @@ -8,16 +8,17 @@ #ifndef __USER_TDFB_H__ #define __USER_TDFB_H__ +#include #include #define SOF_TDFB_NUM_INPUT_PINS 1 /* One source */ #define SOF_TDFB_NUM_OUTPUT_PINS 1 /* One sink */ -#define SOF_TDFB_MAX_SIZE 4096 /* Max size for coef data in bytes */ +#define SOF_TDFB_MAX_SIZE 16384 /* Max size for coef data in bytes */ #define SOF_TDFB_FIR_MAX_LENGTH 256 /* Max length for individual filter */ #define SOF_TDFB_FIR_MAX_COUNT 16 /* A blob can define max 16 FIR EQs */ #define SOF_TDFB_MAX_STREAMS 8 /* Support 1..8 sinks */ #define SOF_TDFB_MAX_ANGLES 360 /* Up to 1 degree precision for 360 degrees coverage */ -#define SOF_TDFB_MAX_MICROPHONES 16 /* Up to 16 microphone locations */ +#define SOF_TDFB_MAX_MICROPHONES PLATFORM_MAX_CHANNELS /* Bounded by direction state arrays */ /* The driver assigns running numbers for control index. If there's single control of * type switch, enum, binary they all have index 0. @@ -28,6 +29,13 @@ #define SOF_TDFB_CTRL_INDEX_AZIMUTH_ESTIMATE 1 /* enum */ #define SOF_TDFB_CTRL_INDEX_FILTERBANK 0 /* bytes */ +/* Helper for magic value for number of words for input_channel_select, output_channel_mix, + * and output_stream_mix. It used to validate that the blob size is sufficient for all the + * filters defined for controllable beam angles. The words count is in the check code + * multiplied with num_filters and sizeof(). See the configuration data description below: + */ +#define SOF_TDFB_CONFIG_FILTER_CONTROL_NUM_WORDS 3 + /* * sof_tdfb_config data[] diff --git a/src/audio/tdfb/tdfb_comp.h b/src/audio/tdfb/tdfb_comp.h index ef33f28fa612..f59ff48bacaf 100644 --- a/src/audio/tdfb/tdfb_comp.h +++ b/src/audio/tdfb/tdfb_comp.h @@ -88,6 +88,9 @@ struct tdfb_comp_data { int16_t *output_stream_mix; /**< for each FIR define stream */ int16_t az_value; /**< beam steer azimuth as in control enum */ int16_t az_value_estimate; /**< beam steer azimuth as in control enum */ + int16_t source_channels; /**< source channel count cached for validator */ + int16_t sink_channels; /**< sink channel count cached for validator */ + size_t config_size; /**< size of the configuration blob */ size_t fir_delay_size; /**< allocated size */ unsigned int max_frames; /**< max frames to process */ bool direction_updates:1; /**< set true if direction angle control is updated */ diff --git a/src/audio/tdfb/tdfb_direction.c b/src/audio/tdfb/tdfb_direction.c index 71b544003ee7..bd435fa2ebd3 100644 --- a/src/audio/tdfb/tdfb_direction.c +++ b/src/audio/tdfb/tdfb_direction.c @@ -386,7 +386,7 @@ static int16_t distance_from_source(struct tdfb_comp_data *cd, int mic_n, static void theoretical_time_differences(struct tdfb_comp_data *cd, int16_t az) { - int16_t d[PLATFORM_MAX_CHANNELS]; + int16_t d[SOF_TDFB_MAX_MICROPHONES]; int16_t src_x; int16_t src_y; int16_t sin_az; diff --git a/src/audio/tdfb/tdfb_ipc3.c b/src/audio/tdfb/tdfb_ipc3.c index 544acbe9a41f..1ebdb9641ccf 100644 --- a/src/audio/tdfb/tdfb_ipc3.c +++ b/src/audio/tdfb/tdfb_ipc3.c @@ -112,6 +112,9 @@ static int tdfb_cmd_get_value(struct processing_module *mod, struct sof_ipc_ctrl { struct tdfb_comp_data *cd = module_get_private_data(mod); + if (cdata->num_elems == 0 || cdata->num_elems > SOF_IPC_MAX_CHANNELS) + return -EINVAL; + switch (cdata->cmd) { case SOF_CTRL_CMD_ENUM: comp_dbg(mod->dev, "SOF_CTRL_CMD_ENUM index=%d", diff --git a/src/audio/tensorflow/tflm-classify.c b/src/audio/tensorflow/tflm-classify.c index 827f6711bc41..9c93329bce1c 100644 --- a/src/audio/tensorflow/tflm-classify.c +++ b/src/audio/tensorflow/tflm-classify.c @@ -3,8 +3,9 @@ // Copyright(c) 2025 Intel Corporation. All rights reserved. #include -#include #include +#include +#include #include #include #include @@ -174,16 +175,15 @@ int8_t expected_feature_yes[TFLM_FEATURE_SIZE] = { */ static int tflm_process(struct processing_module *mod, - struct input_stream_buffer *input_buffers, - int num_input_buffers, - struct output_stream_buffer *output_buffers, - int num_output_buffers) + struct sof_source **sources, int num_of_sources, + struct sof_sink **sinks, int num_of_sinks) { struct tflm_comp_data *cd = module_get_private_data(mod); struct comp_dev *dev = mod->dev; - struct audio_stream *source = input_buffers[0].data; - struct audio_stream *sink = output_buffers[0].data; - int features = input_buffers[0].size; + size_t frame_bytes = source_get_frame_bytes(sources[0]); + int features = source_get_data_frames_available(sources[0]); + const void *data_ptr, *buf_start; + size_t buf_size; int ret; comp_dbg(dev, "entry"); @@ -192,12 +192,18 @@ static int tflm_process(struct processing_module *mod, * by TFLM_FEATURE_SIZE until buffer empty. */ while (features >= TFLM_FEATURE_ELEM_COUNT) { - cd->tfc.audio_features = source->r_ptr; + ret = source_get_data(sources[0], TFLM_FEATURE_ELEM_COUNT * frame_bytes, + &data_ptr, &buf_start, &buf_size); + if (ret) + return ret; + + cd->tfc.audio_features = data_ptr; cd->tfc.audio_data_size = TFLM_FEATURE_ELEM_COUNT; ret = TF_ProcessClassify(&cd->tfc); if (!ret) { comp_err(dev, "classify failed %s.", cd->tfc.error); + source_release_data(sources[0], 0); return ret; } @@ -207,10 +213,9 @@ static int tflm_process(struct processing_module *mod, cd->tfc.predictions[i], prediction[i]); } - /* calc new free and available after moving onto next feature */ - module_update_buffer_position(&input_buffers[0], - &output_buffers[0], TFLM_FEATURE_SIZE); - features = input_buffers[0].size; + /* advance by one stride */ + source_release_data(sources[0], TFLM_FEATURE_SIZE * frame_bytes); + features = source_get_data_frames_available(sources[0]); } return ret; @@ -226,7 +231,7 @@ static int tflm_reset(struct processing_module *mod) static const struct module_interface tflmcly_interface = { .init = tflm_init, // .prepare = tflm_prepare, - .process_audio_stream = tflm_process, + .process = tflm_process, .set_configuration = tflm_set_config, // .get_configuration = tflm_get_config, .reset = tflm_reset, diff --git a/src/audio/tone/tone-ipc3.c b/src/audio/tone/tone-ipc3.c index b2f153696c20..db36b9a88cbe 100644 --- a/src/audio/tone/tone-ipc3.c +++ b/src/audio/tone/tone-ipc3.c @@ -401,8 +401,7 @@ static SHARED_DATA struct comp_driver_info comp_tone_info = { UT_STATIC void sys_comp_tone_init(void) { - comp_register(platform_shared_get(&comp_tone_info, - sizeof(comp_tone_info))); + comp_register(&comp_tone_info); } DECLARE_MODULE(sys_comp_tone_init); diff --git a/src/audio/up_down_mixer/up_down_mixer.c b/src/audio/up_down_mixer/up_down_mixer.c index a5843026710a..270c114ee93c 100644 --- a/src/audio/up_down_mixer/up_down_mixer.c +++ b/src/audio/up_down_mixer/up_down_mixer.c @@ -307,6 +307,13 @@ static int init_mix(struct processing_module *mod, return -EINVAL; } + /* select_mix_out_*() return NULL for unsupported in/out combos */ + if (!cd->mix_routine) { + comp_err(dev, "unsupported channel configuration (in=%d out=%d)", + format->ch_cfg, out_channel_config); + return -EINVAL; + } + /* Update audio format. */ cd->out_fmt[0].valid_bit_depth = IPC4_DEPTH_24BIT; cd->out_fmt[0].depth = IPC4_DEPTH_32BIT; @@ -322,6 +329,9 @@ static int up_down_mixer_free(struct processing_module *mod) { struct up_down_mixer_data *cd = module_get_private_data(mod); + if (!cd) + return 0; + mod_free(mod, cd->buf_in); mod_free(mod, cd->buf_out); mod_free(mod, cd); @@ -339,10 +349,8 @@ static int up_down_mixer_init(struct processing_module *mod) int ret; cd = mod_zalloc(mod, sizeof(*cd)); - if (!cd) { - comp_free(dev); + if (!cd) return -ENOMEM; - } mod_data->private = cd; diff --git a/src/audio/up_down_mixer/up_down_mixer_hifi3.c b/src/audio/up_down_mixer/up_down_mixer_hifi3.c index 259cf5128b9f..87540d02e8e3 100644 --- a/src/audio/up_down_mixer/up_down_mixer_hifi3.c +++ b/src/audio/up_down_mixer/up_down_mixer_hifi3.c @@ -1247,7 +1247,7 @@ void downmix16bit_4ch_mono(struct up_down_mixer_data *cd, const uint8_t * const uint32_t idx3 = get_channel_index(cd->in_channel_map, 2); uint32_t idx4 = get_channel_index(cd->in_channel_map, 3); - uint16_t coeffs[4] = {cd->downmix_coefficients[idx1], + uint16_t coeffs[4] __attribute__((aligned(8))) = {cd->downmix_coefficients[idx1], cd->downmix_coefficients[idx2], cd->downmix_coefficients[idx3], cd->downmix_coefficients[idx4] diff --git a/src/audio/volume/volume.c b/src/audio/volume/volume.c index a6cff8a09083..d43f314e1674 100644 --- a/src/audio/volume/volume.c +++ b/src/audio/volume/volume.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -51,30 +52,31 @@ LOG_MODULE_REGISTER(volume, CONFIG_SOF_LOG_LEVEL); #if CONFIG_FORMAT_S16LE /** * \brief Used to find nearest zero crossing frame for 16 bit format. - * \param[in,out] source Source buffer. + * \param[in,out] source Source circular buffer view. + * \param[in] channels Number of channels in the stream. * \param[in] frames Number of frames. * \param[in,out] prev_sum Previous sum of channel samples. */ -static uint32_t vol_zc_get_s16(const struct audio_stream *source, +static uint32_t vol_zc_get_s16(struct cir_buf_source *source, const int channels, uint32_t frames, int64_t *prev_sum) { uint32_t curr_frames = frames; int32_t sum; - int16_t *x = audio_stream_get_rptr(source); + const int16_t *x = source->ptr; int bytes; int nmax; int i, j, n; - const int nch = audio_stream_get_channels(source); - int remaining_samples = frames * nch; + int remaining_samples = frames * channels; - x = audio_stream_wrap(source, x + remaining_samples - 1); /* Go to last channel */ + /* Go to last channel */ + x = cir_buf_wrap(x + remaining_samples - 1, source->buf_start, source->buf_end); while (remaining_samples) { - bytes = audio_stream_rewind_bytes_without_wrap(source, x); + bytes = cir_buf_bytes_without_wrap_rewind(x, source->buf_start); nmax = VOL_BYTES_TO_S16_SAMPLES(bytes) + 1; n = MIN(nmax, remaining_samples); - for (i = 0; i < n; i += nch) { + for (i = 0; i < n; i += channels) { sum = 0; - for (j = 0; j < nch; j++) { + for (j = 0; j < channels; j++) { sum += *x; x--; } @@ -87,7 +89,7 @@ static uint32_t vol_zc_get_s16(const struct audio_stream *source, curr_frames--; } remaining_samples -= n; - x = audio_stream_rewind_wrap(source, x); + x = source_cir_buf_rewind_wrap(x, source->buf_start, source->buf_end); } /* sign change not detected, process all samples */ @@ -99,30 +101,31 @@ static uint32_t vol_zc_get_s16(const struct audio_stream *source, #if CONFIG_FORMAT_S24LE /** * \brief Used to find nearest zero crossing frame for 24 in 32 bit format. - * \param[in,out] source Source buffer. + * \param[in,out] source Source circular buffer view. + * \param[in] channels Number of channels in the stream. * \param[in] frames Number of frames. * \param[in,out] prev_sum Previous sum of channel samples. */ -static uint32_t vol_zc_get_s24(const struct audio_stream *source, +static uint32_t vol_zc_get_s24(struct cir_buf_source *source, const int channels, uint32_t frames, int64_t *prev_sum) { int64_t sum; uint32_t curr_frames = frames; - int32_t *x = audio_stream_get_rptr(source); + const int32_t *x = source->ptr; int bytes; int nmax; int i, j, n; - const int nch = audio_stream_get_channels(source); - int remaining_samples = frames * nch; + int remaining_samples = frames * channels; - x = audio_stream_wrap(source, x + remaining_samples - 1); /* Go to last channel */ + /* Go to last channel */ + x = cir_buf_wrap(x + remaining_samples - 1, source->buf_start, source->buf_end); while (remaining_samples) { - bytes = audio_stream_rewind_bytes_without_wrap(source, x); + bytes = cir_buf_bytes_without_wrap_rewind(x, source->buf_start); nmax = VOL_BYTES_TO_S32_SAMPLES(bytes) + 1; n = MIN(nmax, remaining_samples); - for (i = 0; i < n; i += nch) { + for (i = 0; i < n; i += channels) { sum = 0; - for (j = 0; j < nch; j++) { + for (j = 0; j < channels; j++) { sum += sign_extend_s24(*x); x--; } @@ -135,7 +138,7 @@ static uint32_t vol_zc_get_s24(const struct audio_stream *source, curr_frames--; } remaining_samples -= n; - x = audio_stream_rewind_wrap(source, x); + x = source_cir_buf_rewind_wrap(x, source->buf_start, source->buf_end); } /* sign change not detected, process all samples */ @@ -147,30 +150,31 @@ static uint32_t vol_zc_get_s24(const struct audio_stream *source, #if CONFIG_FORMAT_S32LE /** * \brief Used to find nearest zero crossing frame for 32 bit format. - * \param[in,out] source Source buffer. + * \param[in,out] source Source circular buffer view. + * \param[in] channels Number of channels in the stream. * \param[in] frames Number of frames. * \param[in,out] prev_sum Previous sum of channel samples. */ -static uint32_t vol_zc_get_s32(const struct audio_stream *source, +static uint32_t vol_zc_get_s32(struct cir_buf_source *source, const int channels, uint32_t frames, int64_t *prev_sum) { int64_t sum; uint32_t curr_frames = frames; - int32_t *x = audio_stream_get_rptr(source); + const int32_t *x = source->ptr; int bytes; int nmax; int i, j, n; - const int nch = audio_stream_get_channels(source); - int remaining_samples = frames * nch; + int remaining_samples = frames * channels; - x = audio_stream_wrap(source, x + remaining_samples - 1); /* Go to last channel */ + /* Go to last channel */ + x = cir_buf_wrap(x + remaining_samples - 1, source->buf_start, source->buf_end); while (remaining_samples) { - bytes = audio_stream_rewind_bytes_without_wrap(source, x); + bytes = cir_buf_bytes_without_wrap_rewind(x, source->buf_start); nmax = VOL_BYTES_TO_S32_SAMPLES(bytes) + 1; n = MIN(nmax, remaining_samples); - for (i = 0; i < n; i += nch) { + for (i = 0; i < n; i += channels) { sum = 0; - for (j = 0; j < nch; j++) { + for (j = 0; j < channels; j++) { sum += *x; x--; } @@ -183,7 +187,7 @@ static uint32_t vol_zc_get_s32(const struct audio_stream *source, curr_frames--; } remaining_samples -= n; - x = audio_stream_rewind_wrap(source, x); + x = source_cir_buf_rewind_wrap(x, source->buf_start, source->buf_end); } /* sign change not detected, process all samples */ @@ -552,17 +556,46 @@ void volume_set_chan_unmute(struct processing_module *mod, int chan) * \return Error code. */ static int volume_process(struct processing_module *mod, - struct input_stream_buffer *input_buffers, int num_input_buffers, - struct output_stream_buffer *output_buffers, int num_output_buffers) + struct sof_source **sources, int num_of_sources, + struct sof_sink **sinks, int num_of_sinks) { struct vol_data *cd = module_get_private_data(mod); - struct audio_stream *source = input_buffers[0].data; - uint32_t avail_frames = input_buffers[0].size; + struct sof_source *source = sources[0]; + struct sof_sink *sink = sinks[0]; + struct cir_buf_source source_buf; + struct cir_buf_sink sink_buf; + const int nch = cd->channels; + size_t source_frame_bytes = source_get_frame_bytes(source); + size_t sink_frame_bytes = sink_get_frame_bytes(sink); + size_t source_bytes, sink_bytes, bytes; + uint32_t avail_frames; uint32_t frames; int64_t prev_sum = 0; + int ret; comp_dbg(mod->dev, "entry"); + avail_frames = source_sink_avail_frames_aligned(source, sink); + if (!avail_frames) + return 0; + + source_bytes = avail_frames * source_frame_bytes; + sink_bytes = avail_frames * sink_frame_bytes; + + /* acquire source and sink buffers once for the whole available period */ + ret = source_get_data(source, source_bytes, &source_buf.ptr, + &source_buf.buf_start, &bytes); + if (ret < 0) + return ret; + source_buf.buf_end = (const char *)source_buf.buf_start + bytes; + + ret = sink_get_buffer(sink, sink_bytes, &sink_buf.ptr, &sink_buf.buf_start, &bytes); + if (ret < 0) { + source_release_data(source, 0); + return ret; + } + sink_buf.buf_end = (char *)sink_buf.buf_start + bytes; + while (avail_frames) { #if CONFIG_COMP_PEAK_VOL volume_update_current_vol_ipc4(cd); @@ -572,19 +605,19 @@ static int volume_process(struct processing_module *mod, frames = avail_frames; } else if (cd->ramp_type == SOF_VOLUME_LINEAR_ZC) { /* with ZC ramping look for next ZC offset */ - frames = cd->zc_get(source, cd->vol_ramp_frames, &prev_sum); + frames = cd->zc_get(&source_buf, nch, cd->vol_ramp_frames, &prev_sum); /* Align frames count to audio stream constraints. If it rounds to zero * round it up to smallest nonzero aligned frames count. */ - frames = audio_stream_align_frames_round_nearest(source, frames); + frames = source_align_frames_round_nearest(source, frames); if (!frames) - frames = audio_stream_align_frames_round_up(source, 1); + frames = source_align_frames_round_up(source, 1); } else { /* During volume ramp align the number of frames used in this * gain step. Align up since with low rates this would typically * become zero. */ - frames = audio_stream_align_frames_round_up(source, cd->vol_ramp_frames); + frames = source_align_frames_round_up(source, cd->vol_ramp_frames); } /* Cancel the gain step for ZC or smaller ramp step if it exceeds @@ -599,10 +632,22 @@ static int volume_process(struct processing_module *mod, } /* copy and scale volume */ - cd->scale_vol(mod, &input_buffers[0], &output_buffers[0], frames, cd->attenuation); + cd->scale_vol(mod, &source_buf, &sink_buf, frames, cd->attenuation); + + /* advance the views by the processed frames */ + source_buf.ptr = cir_buf_wrap((const char *)source_buf.ptr + + frames * source_frame_bytes, + source_buf.buf_start, source_buf.buf_end); + sink_buf.ptr = cir_buf_wrap((char *)sink_buf.ptr + frames * sink_frame_bytes, + sink_buf.buf_start, sink_buf.buf_end); avail_frames -= frames; } + + /* commit the consumed and produced data */ + source_release_data(source, source_bytes); + sink_commit_buffer(sink, sink_bytes); + #if CONFIG_COMP_PEAK_VOL cd->peak_cnt++; if (cd->peak_cnt == cd->peak_report_cnt) { @@ -623,13 +668,13 @@ static int volume_process(struct processing_module *mod, * \param[in,out] dev Volume base component device. */ static vol_zc_func vol_get_zc_function(struct comp_dev *dev, - struct comp_buffer *sinkb) + struct sof_sink *sink) { int i; /* map the zc function to frame format */ for (i = 0; i < ARRAY_SIZE(zc_func_map); i++) { - if (audio_stream_get_valid_fmt(&sinkb->stream) == zc_func_map[i].frame_fmt) + if (sink_get_valid_fmt(sink) == zc_func_map[i].frame_fmt) return zc_func_map[i].func; } @@ -640,9 +685,9 @@ static vol_zc_func vol_get_zc_function(struct comp_dev *dev, * \brief Set volume frames alignment limit. * \param[in,out] source Structure pointer of source. */ -static void volume_set_alignment(struct audio_stream *source) +static void volume_set_alignment(struct sof_source *source) { - const int channels = audio_stream_get_channels(source); + const int channels = source_get_channels(source); /* The source buffer in HiFi5 processing version needs 16 bytes alignment. The * macro SOF_FRAME_BYTE_ALIGN is set in common.h to the requirement align. @@ -662,14 +707,14 @@ static void volume_set_alignment(struct audio_stream *source) * frame align need to be 4, 2, 1, 2, ... */ #if SOF_USE_HIFI(5, VOLUME) - const int n = (audio_stream_get_valid_fmt(source) == SOF_IPC_FRAME_S16_LE) ? 8 : 4; + const int n = (source_get_valid_fmt(source) == SOF_IPC_FRAME_S16_LE) ? 8 : 4; #else - const int n = (audio_stream_get_valid_fmt(source) == SOF_IPC_FRAME_S16_LE) ? 4 : 2; + const int n = (source_get_valid_fmt(source) == SOF_IPC_FRAME_S16_LE) ? 4 : 2; #endif const uint32_t frame_align_req = (uint32_t)(n / gcd(n, channels)); - audio_stream_set_align(byte_align, frame_align_req, source); + source_set_alignment_constants(source, byte_align, frame_align_req); } /** @@ -688,7 +733,6 @@ static int volume_prepare(struct processing_module *mod, struct vol_data *cd = module_get_private_data(mod); struct module_data *md = &mod->priv; struct comp_dev *dev = mod->dev; - struct comp_buffer *sourceb, *sinkb; uint32_t sink_period_bytes; int ret; int i; @@ -696,27 +740,17 @@ static int volume_prepare(struct processing_module *mod, comp_dbg(dev, "entry"); /* volume component will only ever have 1 sink and source buffer */ - sinkb = comp_dev_get_first_data_consumer(dev); - sourceb = comp_dev_get_first_data_producer(dev); - if (!sourceb || !sinkb) { + if (num_of_sources < 1 || num_of_sinks < 1 ) { comp_err(dev, "no source or sink buffer"); return -ENOTCONN; } ret = volume_peak_prepare(cd, mod); - volume_set_alignment(&sourceb->stream); + volume_set_alignment(sources[0]); /* get sink period bytes */ - sink_period_bytes = audio_stream_period_bytes(&sinkb->stream, - dev->frames); - - if (audio_stream_get_size(&sinkb->stream) < sink_period_bytes) { - comp_err(dev, "sink buffer size %d is insufficient < %d", - audio_stream_get_size(&sinkb->stream), sink_period_bytes); - ret = -ENOMEM; - goto err; - } + sink_period_bytes = dev->frames * sink_get_frame_bytes(sinks[0]); set_volume_process(cd, dev, false); @@ -727,7 +761,7 @@ static int volume_prepare(struct processing_module *mod, goto err; } - cd->zc_get = vol_get_zc_function(dev, sinkb); + cd->zc_get = vol_get_zc_function(dev, sinks[0]); if (!cd->zc_get) { comp_err(dev, "invalid cd->zc_get"); ret = -EINVAL; @@ -742,14 +776,14 @@ static int volume_prepare(struct processing_module *mod, */ cd->ramp_finished = false; - cd->channels = audio_stream_get_channels(&sinkb->stream); + cd->channels = sink_get_channels(sinks[0]); if (cd->channels > SOF_IPC_MAX_CHANNELS) { ret = -EINVAL; goto err; } cd->sample_rate_inv = (int32_t)(1000LL * INT32_MAX / - audio_stream_get_rate(&sinkb->stream)); + sink_get_rate(sinks[0])); for (i = 0; i < cd->channels; i++) { cd->volume[i] = cd->vol_min; @@ -808,7 +842,7 @@ static int volume_free(struct processing_module *mod) static const struct module_interface volume_interface = { .init = volume_init, .prepare = volume_prepare, - .process_audio_stream = volume_process, + .process = volume_process, .set_configuration = volume_set_config, .get_configuration = volume_get_config, .reset = volume_reset, @@ -819,7 +853,7 @@ static const struct module_interface volume_interface = { static const struct module_interface gain_interface = { .init = volume_init, .prepare = volume_prepare, - .process_audio_stream = volume_process, + .process = volume_process, .set_configuration = volume_set_config, .get_configuration = volume_get_config, .reset = volume_reset, diff --git a/src/audio/volume/volume.h b/src/audio/volume/volume.h index 9d1d08a280fe..478e80673089 100644 --- a/src/audio/volume/volume.h +++ b/src/audio/volume/volume.h @@ -146,14 +146,18 @@ struct sof_ipc_ctrl_value_chan; /** * \brief volume processing function interface + * + * The source and sink circular buffers are acquired and committed once by the + * module's process callback. Each processing function receives ready-to-use + * circular buffer views and only performs the gain scaling. */ -typedef void (*vol_scale_func)(struct processing_module *mod, struct input_stream_buffer *source, - struct output_stream_buffer *sink, uint32_t frames, uint32_t attenuation); +typedef void (*vol_scale_func)(struct processing_module *mod, struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation); /** * \brief volume interface for function getting nearest zero crossing frame */ -typedef uint32_t (*vol_zc_func)(const struct audio_stream *source, +typedef uint32_t (*vol_zc_func)(struct cir_buf_source *source, const int channels, uint32_t frames, int64_t *prev_sum); /** diff --git a/src/audio/volume/volume_generic.c b/src/audio/volume/volume_generic.c index 0830677c7c94..a721823dd766 100644 --- a/src/audio/volume/volume_generic.c +++ b/src/audio/volume/volume_generic.c @@ -56,29 +56,23 @@ static inline int32_t vol_mult_s24_to_s24(int32_t x, int32_t vol) * Copy and scale volume from 24/32 bit source buffer * to 24/32 bit destination buffer. */ -static void vol_s24_to_s24(struct processing_module *mod, struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, - uint32_t attenuation) +static void vol_s24_to_s24(struct processing_module *mod, struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { struct vol_data *cd = module_get_private_data(mod); - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; - int32_t vol; - int32_t *x, *x0; + const int32_t *x, *x0; int32_t *y, *y0; + int32_t vol; int nmax, n, i, j; - const int nch = audio_stream_get_channels(source); + const int nch = cd->channels; int remaining_samples = frames * nch; - x = audio_stream_wrap(source, (char *)audio_stream_get_rptr(source) + bsource->consumed); - y = audio_stream_wrap(sink, (char *)audio_stream_get_wptr(sink) + bsink->size); - - bsource->consumed += VOL_S32_SAMPLES_TO_BYTES(remaining_samples); - bsink->size += VOL_S32_SAMPLES_TO_BYTES(remaining_samples); + x = source->ptr; + y = sink->ptr; while (remaining_samples) { - nmax = audio_stream_samples_without_wrap_s24(source, x); + nmax = cir_buf_samples_without_wrap_s32(x, source->buf_end); n = MIN(remaining_samples, nmax); - nmax = audio_stream_samples_without_wrap_s24(sink, y); + nmax = cir_buf_samples_without_wrap_s32(y, sink->buf_end); n = MIN(n, nmax); for (j = 0; j < nch; j++) { x0 = x + j; @@ -89,8 +83,8 @@ static void vol_s24_to_s24(struct processing_module *mod, struct input_stream_bu } } remaining_samples -= n; - x = audio_stream_wrap(source, x + n); - y = audio_stream_wrap(sink, y + n); + x = cir_buf_wrap(x + n, source->buf_start, source->buf_end); + y = cir_buf_wrap(y + n, sink->buf_start, sink->buf_end); } } @@ -106,32 +100,28 @@ static void vol_s24_to_s24(struct processing_module *mod, struct input_stream_bu * to 24/32 bit destination buffer. */ static void vol_passthrough_s24_to_s24(struct processing_module *mod, - struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, + struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; - int32_t *x; + struct vol_data *cd = module_get_private_data(mod); + const int32_t *x; int32_t *y; int nmax, n; - const int nch = audio_stream_get_channels(source); + const int nch = cd->channels; int remaining_samples = frames * nch; - x = audio_stream_wrap(source, (char *)audio_stream_get_rptr(source) + bsource->consumed); - y = audio_stream_wrap(sink, (char *)audio_stream_get_wptr(sink) + bsink->size); - - bsource->consumed += VOL_S32_SAMPLES_TO_BYTES(remaining_samples); - bsink->size += VOL_S32_SAMPLES_TO_BYTES(remaining_samples); + x = source->ptr; + y = sink->ptr; while (remaining_samples) { - nmax = audio_stream_samples_without_wrap_s24(source, x); + nmax = cir_buf_samples_without_wrap_s32(x, source->buf_end); n = MIN(remaining_samples, nmax); - nmax = audio_stream_samples_without_wrap_s24(sink, y); + nmax = cir_buf_samples_without_wrap_s32(y, sink->buf_end); n = MIN(n, nmax); memcpy_s(y, n * sizeof(int32_t), x, n * sizeof(int32_t)); remaining_samples -= n; - x = audio_stream_wrap(source, x + n); - y = audio_stream_wrap(sink, y + n); + x = cir_buf_wrap(x + n, source->buf_start, source->buf_end); + y = cir_buf_wrap(y + n, sink->buf_start, sink->buf_end); } } #endif /* CONFIG_FORMAT_S24LE */ @@ -148,28 +138,23 @@ static void vol_passthrough_s24_to_s24(struct processing_module *mod, * Copy and scale volume from 32 bit source buffer * to 32 bit destination buffer. */ -static void vol_s32_to_s32(struct processing_module *mod, struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, - uint32_t attenuation) +static void vol_s32_to_s32(struct processing_module *mod, struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { struct vol_data *cd = module_get_private_data(mod); - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; - int32_t vol; - int32_t *x, *x0; + const int32_t *x, *x0; int32_t *y, *y0; + int32_t vol; int nmax, n, i, j; - const int nch = audio_stream_get_channels(source); + const int nch = cd->channels; int remaining_samples = frames * nch; - x = audio_stream_wrap(source, (char *)audio_stream_get_rptr(source) + bsource->consumed); - y = audio_stream_wrap(sink, (char *)audio_stream_get_wptr(sink) + bsink->size); - bsource->consumed += VOL_S32_SAMPLES_TO_BYTES(remaining_samples); - bsink->size += VOL_S32_SAMPLES_TO_BYTES(remaining_samples); + x = source->ptr; + y = sink->ptr; while (remaining_samples) { - nmax = audio_stream_samples_without_wrap_s32(source, x); + nmax = cir_buf_samples_without_wrap_s32(x, source->buf_end); n = MIN(remaining_samples, nmax); - nmax = audio_stream_samples_without_wrap_s32(sink, y); + nmax = cir_buf_samples_without_wrap_s32(y, sink->buf_end); n = MIN(n, nmax); /* Note: on Xtensa processing one channel volume at time performed slightly * better than simpler interleaved code version (average 19 us vs. 20 us). @@ -184,8 +169,8 @@ static void vol_s32_to_s32(struct processing_module *mod, struct input_stream_bu } } remaining_samples -= n; - x = audio_stream_wrap(source, x + n); - y = audio_stream_wrap(sink, y + n); + x = cir_buf_wrap(x + n, source->buf_start, source->buf_end); + y = cir_buf_wrap(y + n, sink->buf_start, sink->buf_end); } } @@ -201,31 +186,28 @@ static void vol_s32_to_s32(struct processing_module *mod, struct input_stream_bu * to 32 bit destination buffer. */ static void vol_passthrough_s32_to_s32(struct processing_module *mod, - struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, + struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; - int32_t *x; + struct vol_data *cd = module_get_private_data(mod); + const int32_t *x; int32_t *y; int nmax, n; - const int nch = audio_stream_get_channels(source); + const int nch = cd->channels; int remaining_samples = frames * nch; - x = audio_stream_wrap(source, (char *)audio_stream_get_rptr(source) + bsource->consumed); - y = audio_stream_wrap(sink, (char *)audio_stream_get_wptr(sink) + bsink->size); - bsource->consumed += VOL_S32_SAMPLES_TO_BYTES(remaining_samples); - bsink->size += VOL_S32_SAMPLES_TO_BYTES(remaining_samples); + x = source->ptr; + y = sink->ptr; while (remaining_samples) { - nmax = audio_stream_samples_without_wrap_s32(source, x); + nmax = cir_buf_samples_without_wrap_s32(x, source->buf_end); n = MIN(remaining_samples, nmax); - nmax = audio_stream_samples_without_wrap_s32(sink, y); + nmax = cir_buf_samples_without_wrap_s32(y, sink->buf_end); n = MIN(n, nmax); memcpy_s(y, n * sizeof(int32_t), x, n * sizeof(int32_t)); remaining_samples -= n; - x = audio_stream_wrap(source, x + n); - y = audio_stream_wrap(sink, y + n); + x = cir_buf_wrap(x + n, source->buf_start, source->buf_end); + y = cir_buf_wrap(y + n, sink->buf_start, sink->buf_end); } } #endif /* CONFIG_FORMAT_S32LE */ @@ -242,28 +224,23 @@ static void vol_passthrough_s32_to_s32(struct processing_module *mod, * Copy and scale volume from 16 bit source buffer * to 16 bit destination buffer. */ -static void vol_s16_to_s16(struct processing_module *mod, struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, - uint32_t attenuation) +static void vol_s16_to_s16(struct processing_module *mod, struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { struct vol_data *cd = module_get_private_data(mod); - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; - int32_t vol; - int16_t *x, *x0; + const int16_t *x, *x0; int16_t *y, *y0; + int32_t vol; int nmax, n, i, j; - const int nch = audio_stream_get_channels(source); + const int nch = cd->channels; int remaining_samples = frames * nch; - x = audio_stream_wrap(source, (char *)audio_stream_get_rptr(source) + bsource->consumed); - y = audio_stream_wrap(sink, (char *)audio_stream_get_wptr(sink) + bsink->size); - bsource->consumed += VOL_S16_SAMPLES_TO_BYTES(remaining_samples); - bsink->size += VOL_S16_SAMPLES_TO_BYTES(remaining_samples); + x = source->ptr; + y = sink->ptr; while (remaining_samples) { - nmax = audio_stream_samples_without_wrap_s16(source, x); + nmax = cir_buf_samples_without_wrap_s16(x, source->buf_end); n = MIN(remaining_samples, nmax); - nmax = audio_stream_samples_without_wrap_s16(sink, y); + nmax = cir_buf_samples_without_wrap_s16(y, sink->buf_end); n = MIN(n, nmax); for (j = 0; j < nch; j++) { x0 = x + j; @@ -275,8 +252,8 @@ static void vol_s16_to_s16(struct processing_module *mod, struct input_stream_bu } } remaining_samples -= n; - x = audio_stream_wrap(source, x + n); - y = audio_stream_wrap(sink, y + n); + x = cir_buf_wrap(x + n, source->buf_start, source->buf_end); + y = cir_buf_wrap(y + n, sink->buf_start, sink->buf_end); } } @@ -292,31 +269,28 @@ static void vol_s16_to_s16(struct processing_module *mod, struct input_stream_bu * to 16 bit destination buffer. */ static void vol_passthrough_s16_to_s16(struct processing_module *mod, - struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, + struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; - int16_t *x; + struct vol_data *cd = module_get_private_data(mod); + const int16_t *x; int16_t *y; int nmax, n; - const int nch = audio_stream_get_channels(source); + const int nch = cd->channels; int remaining_samples = frames * nch; - x = audio_stream_wrap(source, (char *)audio_stream_get_rptr(source) + bsource->consumed); - y = audio_stream_wrap(sink, (char *)audio_stream_get_wptr(sink) + bsink->size); - bsource->consumed += VOL_S16_SAMPLES_TO_BYTES(remaining_samples); - bsink->size += VOL_S16_SAMPLES_TO_BYTES(remaining_samples); + x = source->ptr; + y = sink->ptr; while (remaining_samples) { - nmax = audio_stream_samples_without_wrap_s16(source, x); + nmax = cir_buf_samples_without_wrap_s16(x, source->buf_end); n = MIN(remaining_samples, nmax); - nmax = audio_stream_samples_without_wrap_s16(sink, y); + nmax = cir_buf_samples_without_wrap_s16(y, sink->buf_end); n = MIN(n, nmax); memcpy_s(y, n * sizeof(int16_t), x, n * sizeof(int16_t)); remaining_samples -= n; - x = audio_stream_wrap(source, x + n); - y = audio_stream_wrap(sink, y + n); + x = cir_buf_wrap(x + n, source->buf_start, source->buf_end); + y = cir_buf_wrap(y + n, sink->buf_start, sink->buf_end); } } #endif /* CONFIG_FORMAT_S16LE */ diff --git a/src/audio/volume/volume_generic_with_peakvol.c b/src/audio/volume/volume_generic_with_peakvol.c index 106e1d3c7bdd..dbca458ed4eb 100644 --- a/src/audio/volume/volume_generic_with_peakvol.c +++ b/src/audio/volume/volume_generic_with_peakvol.c @@ -52,30 +52,24 @@ static inline int32_t vol_mult_s24_to_s24(int32_t x, int32_t vol) * Copy and scale volume from 24/32 bit source buffer * to 24/32 bit destination buffer. */ -static void vol_s24_to_s24(struct processing_module *mod, struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, - uint32_t attenuation) +static void vol_s24_to_s24(struct processing_module *mod, struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { struct vol_data *cd = module_get_private_data(mod); - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; - int32_t vol; - int32_t *x, *x0; + const int32_t *x, *x0; int32_t *y, *y0; + int32_t vol; + int32_t tmp; int nmax, n, i, j; - const int nch = audio_stream_get_channels(source); + const int nch = cd->channels; int remaining_samples = frames * nch; - int32_t tmp; - - x = audio_stream_wrap(source, (char *)audio_stream_get_rptr(source) + bsource->consumed); - y = audio_stream_wrap(sink, (char *)audio_stream_get_wptr(sink) + bsink->size); - bsource->consumed += VOL_S32_SAMPLES_TO_BYTES(remaining_samples); - bsink->size += VOL_S32_SAMPLES_TO_BYTES(remaining_samples); + x = source->ptr; + y = sink->ptr; while (remaining_samples) { - nmax = audio_stream_samples_without_wrap_s24(source, x); + nmax = cir_buf_samples_without_wrap_s32(x, source->buf_end); n = MIN(remaining_samples, nmax); - nmax = audio_stream_samples_without_wrap_s24(sink, y); + nmax = cir_buf_samples_without_wrap_s32(y, sink->buf_end); n = MIN(n, nmax); for (j = 0; j < nch; j++) { x0 = x + j; @@ -90,8 +84,8 @@ static void vol_s24_to_s24(struct processing_module *mod, struct input_stream_bu cd->peak_regs.peak_meter[j] = MAX(tmp, cd->peak_regs.peak_meter[j]); } remaining_samples -= n; - x = audio_stream_wrap(source, x + n); - y = audio_stream_wrap(sink, y + n); + x = cir_buf_wrap(x + n, source->buf_start, source->buf_end); + y = cir_buf_wrap(y + n, sink->buf_start, sink->buf_end); } } @@ -107,29 +101,24 @@ static void vol_s24_to_s24(struct processing_module *mod, struct input_stream_bu * to 24/32 bit destination buffer. */ static void vol_passthrough_s24_to_s24(struct processing_module *mod, - struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, + struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { struct vol_data *cd = module_get_private_data(mod); - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; - int32_t *x, *x0; + const int32_t *x, *x0; int32_t *y, *y0; + int32_t tmp; int nmax, n, i, j; - const int nch = audio_stream_get_channels(source); + const int nch = cd->channels; int remaining_samples = frames * nch; - int32_t tmp; - - x = audio_stream_wrap(source, (char *)audio_stream_get_rptr(source) + bsource->consumed); - y = audio_stream_wrap(sink, (char *)audio_stream_get_wptr(sink) + bsink->size); - bsource->consumed += VOL_S32_SAMPLES_TO_BYTES(remaining_samples); - bsink->size += VOL_S32_SAMPLES_TO_BYTES(remaining_samples); + x = source->ptr; + y = sink->ptr; while (remaining_samples) { - nmax = audio_stream_samples_without_wrap_s24(source, x); + nmax = cir_buf_samples_without_wrap_s32(x, source->buf_end); n = MIN(remaining_samples, nmax); - nmax = audio_stream_samples_without_wrap_s24(sink, y); + nmax = cir_buf_samples_without_wrap_s32(y, sink->buf_end); n = MIN(n, nmax); for (j = 0; j < nch; j++) { x0 = x + j; @@ -143,8 +132,8 @@ static void vol_passthrough_s24_to_s24(struct processing_module *mod, cd->peak_regs.peak_meter[j] = MAX(tmp, cd->peak_regs.peak_meter[j]); } remaining_samples -= n; - x = audio_stream_wrap(source, x + n); - y = audio_stream_wrap(sink, y + n); + x = cir_buf_wrap(x + n, source->buf_start, source->buf_end); + y = cir_buf_wrap(y + n, sink->buf_start, sink->buf_end); } } #endif /* CONFIG_FORMAT_S24LE */ @@ -161,29 +150,24 @@ static void vol_passthrough_s24_to_s24(struct processing_module *mod, * Copy and scale volume from 32 bit source buffer * to 32 bit destination buffer. */ -static void vol_s32_to_s32(struct processing_module *mod, struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, - uint32_t attenuation) +static void vol_s32_to_s32(struct processing_module *mod, struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { struct vol_data *cd = module_get_private_data(mod); - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; - int32_t vol; - int32_t *x, *x0; + const int32_t *x, *x0; int32_t *y, *y0; + int32_t vol; + int32_t tmp; int nmax, n, i, j; - const int nch = audio_stream_get_channels(source); + const int nch = cd->channels; int remaining_samples = frames * nch; - int32_t tmp; - x = audio_stream_wrap(source, (char *)audio_stream_get_rptr(source) + bsource->consumed); - y = audio_stream_wrap(sink, (char *)audio_stream_get_wptr(sink) + bsink->size); - bsource->consumed += VOL_S32_SAMPLES_TO_BYTES(remaining_samples); - bsink->size += VOL_S32_SAMPLES_TO_BYTES(remaining_samples); + x = source->ptr; + y = sink->ptr; while (remaining_samples) { - nmax = audio_stream_samples_without_wrap_s32(source, x); + nmax = cir_buf_samples_without_wrap_s32(x, source->buf_end); n = MIN(remaining_samples, nmax); - nmax = audio_stream_samples_without_wrap_s32(sink, y); + nmax = cir_buf_samples_without_wrap_s32(y, sink->buf_end); n = MIN(n, nmax); /* Note: on Xtensa processing one channel volume at time performed slightly * better than simpler interleaved code version (average 19 us vs. 20 us). @@ -202,8 +186,8 @@ static void vol_s32_to_s32(struct processing_module *mod, struct input_stream_bu cd->peak_regs.peak_meter[j] = MAX(tmp, cd->peak_regs.peak_meter[j]); } remaining_samples -= n; - x = audio_stream_wrap(source, x + n); - y = audio_stream_wrap(sink, y + n); + x = cir_buf_wrap(x + n, source->buf_start, source->buf_end); + y = cir_buf_wrap(y + n, sink->buf_start, sink->buf_end); } } @@ -219,28 +203,24 @@ static void vol_s32_to_s32(struct processing_module *mod, struct input_stream_bu * to 32 bit destination buffer. */ static void vol_passthrough_s32_to_s32(struct processing_module *mod, - struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, + struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { struct vol_data *cd = module_get_private_data(mod); - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; - int32_t *x, *x0; + const int32_t *x, *x0; int32_t *y, *y0; + int32_t tmp; int nmax, n, i, j; - const int nch = audio_stream_get_channels(source); + const int nch = cd->channels; int remaining_samples = frames * nch; - int32_t tmp; - x = audio_stream_wrap(source, (char *)audio_stream_get_rptr(source) + bsource->consumed); - y = audio_stream_wrap(sink, (char *)audio_stream_get_wptr(sink) + bsink->size); - bsource->consumed += VOL_S32_SAMPLES_TO_BYTES(remaining_samples); - bsink->size += VOL_S32_SAMPLES_TO_BYTES(remaining_samples); + x = source->ptr; + y = sink->ptr; while (remaining_samples) { - nmax = audio_stream_samples_without_wrap_s32(source, x); + nmax = cir_buf_samples_without_wrap_s32(x, source->buf_end); n = MIN(remaining_samples, nmax); - nmax = audio_stream_samples_without_wrap_s32(sink, y); + nmax = cir_buf_samples_without_wrap_s32(y, sink->buf_end); n = MIN(n, nmax); /* Note: on Xtensa processing one channel volume at time performed slightly * better than simpler interleaved code version (average 19 us vs. 20 us). @@ -257,8 +237,8 @@ static void vol_passthrough_s32_to_s32(struct processing_module *mod, cd->peak_regs.peak_meter[j] = MAX(tmp, cd->peak_regs.peak_meter[j]); } remaining_samples -= n; - x = audio_stream_wrap(source, x + n); - y = audio_stream_wrap(sink, y + n); + x = cir_buf_wrap(x + n, source->buf_start, source->buf_end); + y = cir_buf_wrap(y + n, sink->buf_start, sink->buf_end); } } #endif /* CONFIG_FORMAT_S32LE */ @@ -275,30 +255,24 @@ static void vol_passthrough_s32_to_s32(struct processing_module *mod, * Copy and scale volume from 16 bit source buffer * to 16 bit destination buffer. */ -static void vol_s16_to_s16(struct processing_module *mod, struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, - uint32_t attenuation) +static void vol_s16_to_s16(struct processing_module *mod, struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { struct vol_data *cd = module_get_private_data(mod); - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; - int32_t vol; - int16_t *x, *x0; + const int16_t *x, *x0; int16_t *y, *y0; + int32_t vol; + uint32_t tmp; int nmax, n, i, j; - const int nch = audio_stream_get_channels(source); + const int nch = cd->channels; int remaining_samples = frames * nch; - uint32_t tmp; - - x = audio_stream_wrap(source, (char *)audio_stream_get_rptr(source) + bsource->consumed); - y = audio_stream_wrap(sink, (char *)audio_stream_get_wptr(sink) + bsink->size); - bsource->consumed += VOL_S16_SAMPLES_TO_BYTES(remaining_samples); - bsink->size += VOL_S16_SAMPLES_TO_BYTES(remaining_samples); + x = source->ptr; + y = sink->ptr; while (remaining_samples) { - nmax = audio_stream_samples_without_wrap_s16(source, x); + nmax = cir_buf_samples_without_wrap_s16(x, source->buf_end); n = MIN(remaining_samples, nmax); - nmax = audio_stream_samples_without_wrap_s16(sink, y); + nmax = cir_buf_samples_without_wrap_s16(y, sink->buf_end); n = MIN(n, nmax); for (j = 0; j < nch; j++) { x0 = x + j; @@ -314,8 +288,8 @@ static void vol_s16_to_s16(struct processing_module *mod, struct input_stream_bu cd->peak_regs.peak_meter[j] = MAX(tmp, cd->peak_regs.peak_meter[j]); } remaining_samples -= n; - x = audio_stream_wrap(source, x + n); - y = audio_stream_wrap(sink, y + n); + x = cir_buf_wrap(x + n, source->buf_start, source->buf_end); + y = cir_buf_wrap(y + n, sink->buf_start, sink->buf_end); } } @@ -331,29 +305,24 @@ static void vol_s16_to_s16(struct processing_module *mod, struct input_stream_bu * to 16 bit destination buffer. */ static void vol_passthrough_s16_to_s16(struct processing_module *mod, - struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, + struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { struct vol_data *cd = module_get_private_data(mod); - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; - int16_t *x, *x0; + const int16_t *x, *x0; int16_t *y, *y0; + uint32_t tmp; int nmax, n, i, j; - const int nch = audio_stream_get_channels(source); + const int nch = cd->channels; int remaining_samples = frames * nch; - uint32_t tmp; - - x = audio_stream_wrap(source, (char *)audio_stream_get_rptr(source) + bsource->consumed); - y = audio_stream_wrap(sink, (char *)audio_stream_get_wptr(sink) + bsink->size); - bsource->consumed += VOL_S16_SAMPLES_TO_BYTES(remaining_samples); - bsink->size += VOL_S16_SAMPLES_TO_BYTES(remaining_samples); + x = source->ptr; + y = sink->ptr; while (remaining_samples) { - nmax = audio_stream_samples_without_wrap_s16(source, x); + nmax = cir_buf_samples_without_wrap_s16(x, source->buf_end); n = MIN(remaining_samples, nmax); - nmax = audio_stream_samples_without_wrap_s16(sink, y); + nmax = cir_buf_samples_without_wrap_s16(y, sink->buf_end); n = MIN(n, nmax); for (j = 0; j < nch; j++) { x0 = x + j; @@ -367,8 +336,8 @@ static void vol_passthrough_s16_to_s16(struct processing_module *mod, cd->peak_regs.peak_meter[j] = MAX(tmp, cd->peak_regs.peak_meter[j]); } remaining_samples -= n; - x = audio_stream_wrap(source, x + n); - y = audio_stream_wrap(sink, y + n); + x = cir_buf_wrap(x + n, source->buf_start, source->buf_end); + y = cir_buf_wrap(y + n, sink->buf_start, sink->buf_end); } } #endif diff --git a/src/audio/volume/volume_hifi3.c b/src/audio/volume/volume_hifi3.c index afbb8ff44c02..1956993f7282 100644 --- a/src/audio/volume/volume_hifi3.c +++ b/src/audio/volume/volume_hifi3.c @@ -56,13 +56,11 @@ static void vol_store_gain(struct vol_data *cd, const int channels_count) * \param[in] frames Number of frames to process. * \param[in] attenuation factor for peakmeter adjustment (unused) */ -static void vol_s24_to_s24_s32(struct processing_module *mod, struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, +static void vol_s24_to_s24_s32(struct processing_module *mod, struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { struct vol_data *cd = module_get_private_data(mod); - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; ae_f32x2 in_sample; ae_f32x2 out_sample; ae_f32x2 volume; @@ -72,11 +70,9 @@ static void vol_s24_to_s24_s32(struct processing_module *mod, struct input_strea ae_f32x2 *vol; ae_valign inu; ae_valign outu = AE_ZALIGN64(); - ae_f32x2 *in = (ae_f32x2 *)audio_stream_wrap(source, (char *)audio_stream_get_rptr(source) - + bsource->consumed); - ae_f32x2 *out = (ae_f32x2 *)audio_stream_wrap(sink, (char *)audio_stream_get_wptr(sink) - + bsink->size); - const int channels_count = audio_stream_get_channels(sink); + const ae_f32x2 *in = source->ptr; + ae_f32x2 *out = sink->ptr; + const int channels_count = cd->channels; const int inc = sizeof(ae_f32x2); int samples = channels_count * frames; @@ -94,13 +90,10 @@ static void vol_s24_to_s24_s32(struct processing_module *mod, struct input_strea AE_SETCBEGIN0(buf); AE_SETCEND0(buf_end); - bsource->consumed += VOL_S32_SAMPLES_TO_BYTES(samples); - bsink->size += VOL_S32_SAMPLES_TO_BYTES(samples); - while (samples) { - m = audio_stream_samples_without_wrap_s24(source, in); + m = cir_buf_samples_without_wrap_s32(in, source->buf_end); n = MIN(m, samples); - m = audio_stream_samples_without_wrap_s24(sink, out); + m = cir_buf_samples_without_wrap_s32(out, sink->buf_end); n = MIN(m, n); inu = AE_LA64_PP(in); /* process two continuous sample data once */ @@ -130,8 +123,8 @@ static void vol_s24_to_s24_s32(struct processing_module *mod, struct input_strea } AE_SA64POS_FP(outu, out); samples -= n; - in = audio_stream_wrap(source, in); - out = audio_stream_wrap(sink, out); + in = cir_buf_wrap(in, source->buf_start, source->buf_end); + out = cir_buf_wrap(out, sink->buf_start, sink->buf_end); } } @@ -144,30 +137,24 @@ static void vol_s24_to_s24_s32(struct processing_module *mod, struct input_strea * \param[in] attenuation factor for peakmeter adjustment (unused) */ static void vol_passthrough_s24_to_s24_s32(struct processing_module *mod, - struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, + struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; + struct vol_data *cd = module_get_private_data(mod); ae_f32x2 in_sample; int i, n, m; ae_valign inu; ae_valign outu = AE_ZALIGN64(); - ae_f32x2 *in = (ae_f32x2 *)audio_stream_wrap(source, (char *)audio_stream_get_rptr(source) - + bsource->consumed); - ae_f32x2 *out = (ae_f32x2 *)audio_stream_wrap(sink, (char *)audio_stream_get_wptr(sink) - + bsink->size); - const int channels_count = audio_stream_get_channels(sink); + const ae_f32x2 *in = source->ptr; + ae_f32x2 *out = sink->ptr; + const int channels_count = cd->channels; int samples = channels_count * frames; - bsource->consumed += VOL_S32_SAMPLES_TO_BYTES(samples); - bsink->size += VOL_S32_SAMPLES_TO_BYTES(samples); - while (samples) { - m = audio_stream_samples_without_wrap_s24(source, in); + m = cir_buf_samples_without_wrap_s32(in, source->buf_end); n = MIN(m, samples); - m = audio_stream_samples_without_wrap_s24(sink, out); + m = cir_buf_samples_without_wrap_s32(out, sink->buf_end); n = MIN(m, n); inu = AE_LA64_PP(in); /* process two continuous sample data once */ @@ -179,8 +166,8 @@ static void vol_passthrough_s24_to_s24_s32(struct processing_module *mod, } AE_SA64POS_FP(outu, out); samples -= n; - in = audio_stream_wrap(source, in); - out = audio_stream_wrap(sink, out); + in = cir_buf_wrap(in, source->buf_start, source->buf_end); + out = cir_buf_wrap(out, sink->buf_start, sink->buf_end); } } #endif /* CONFIG_FORMAT_S24LE */ @@ -194,13 +181,11 @@ static void vol_passthrough_s24_to_s24_s32(struct processing_module *mod, * \param[in] frames Number of frames to process. * \param[in] attenuation factor for peakmeter adjustment (unused) */ -static void vol_s32_to_s24_s32(struct processing_module *mod, struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, +static void vol_s32_to_s24_s32(struct processing_module *mod, struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { struct vol_data *cd = module_get_private_data(mod); - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; ae_f32x2 in_sample; ae_f32x2 out_sample; ae_f32x2 volume; @@ -210,13 +195,11 @@ static void vol_s32_to_s24_s32(struct processing_module *mod, struct input_strea ae_f32x2 *vol; ae_valign inu; ae_valign outu = AE_ZALIGN64(); - const int channels_count = audio_stream_get_channels(sink); + const int channels_count = cd->channels; const int inc = sizeof(ae_f32x2); int samples = channels_count * frames; - ae_f32x2 *in = (ae_f32x2 *)audio_stream_wrap(source, (char *)audio_stream_get_rptr(source) - + bsource->consumed); - ae_f32x2 *out = (ae_f32x2 *)audio_stream_wrap(sink, (char *)audio_stream_get_wptr(sink) - + bsink->size); + const ae_f32x2 *in = source->ptr; + ae_f32x2 *out = sink->ptr; /* to ensure the address is 8-byte aligned and avoid risk of * error loading of volume gain while the cd->vol would be set @@ -232,13 +215,10 @@ static void vol_s32_to_s24_s32(struct processing_module *mod, struct input_strea AE_SETCBEGIN0(buf); AE_SETCEND0(buf_end); - bsource->consumed += VOL_S32_SAMPLES_TO_BYTES(samples); - bsink->size += VOL_S32_SAMPLES_TO_BYTES(samples); - while (samples) { - m = audio_stream_samples_without_wrap_s32(source, in); + m = cir_buf_samples_without_wrap_s32(in, source->buf_end); n = MIN(m, samples); - m = audio_stream_samples_without_wrap_s32(sink, out); + m = cir_buf_samples_without_wrap_s32(out, sink->buf_end); n = MIN(m, n); inu = AE_LA64_PP(in); /* process two continuous sample data once */ @@ -266,8 +246,8 @@ static void vol_s32_to_s24_s32(struct processing_module *mod, struct input_strea } AE_SA64POS_FP(outu, out); samples -= n; - in = audio_stream_wrap(source, in); - out = audio_stream_wrap(sink, out); + in = cir_buf_wrap(in, source->buf_start, source->buf_end); + out = cir_buf_wrap(out, sink->buf_start, sink->buf_end); } } @@ -280,30 +260,24 @@ static void vol_s32_to_s24_s32(struct processing_module *mod, struct input_strea * \param[in] attenuation factor for peakmeter adjustment (unused) */ static void vol_passthrough_s32_to_s24_s32(struct processing_module *mod, - struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, + struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; + struct vol_data *cd = module_get_private_data(mod); ae_f32x2 in_sample; int i, n, m; ae_valign inu; ae_valign outu = AE_ZALIGN64(); - const int channels_count = audio_stream_get_channels(sink); + const int channels_count = cd->channels; int samples = channels_count * frames; - ae_f32x2 *in = (ae_f32x2 *)audio_stream_wrap(source, (char *)audio_stream_get_rptr(source) - + bsource->consumed); - ae_f32x2 *out = (ae_f32x2 *)audio_stream_wrap(sink, (char *)audio_stream_get_wptr(sink) - + bsink->size); - - bsource->consumed += VOL_S32_SAMPLES_TO_BYTES(samples); - bsink->size += VOL_S32_SAMPLES_TO_BYTES(samples); + const ae_f32x2 *in = source->ptr; + ae_f32x2 *out = sink->ptr; while (samples) { - m = audio_stream_samples_without_wrap_s32(source, in); + m = cir_buf_samples_without_wrap_s32(in, source->buf_end); n = MIN(m, samples); - m = audio_stream_samples_without_wrap_s32(sink, out); + m = cir_buf_samples_without_wrap_s32(out, sink->buf_end); n = MIN(m, n); inu = AE_LA64_PP(in); /* process two continuous sample data once */ @@ -314,8 +288,8 @@ static void vol_passthrough_s32_to_s24_s32(struct processing_module *mod, } AE_SA64POS_FP(outu, out); samples -= n; - in = audio_stream_wrap(source, in); - out = audio_stream_wrap(sink, out); + in = cir_buf_wrap(in, source->buf_start, source->buf_end); + out = cir_buf_wrap(out, sink->buf_start, sink->buf_end); } } #endif /* CONFIG_FORMAT_S32LE */ @@ -329,13 +303,11 @@ static void vol_passthrough_s32_to_s24_s32(struct processing_module *mod, * \param[in] frames Number of frames to process. * \param[in] attenuation factor for peakmeter adjustment (unused) */ -static void vol_s16_to_s16(struct processing_module *mod, struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, +static void vol_s16_to_s16(struct processing_module *mod, struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { struct vol_data *cd = module_get_private_data(mod); - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; ae_f32x2 volume0; ae_f32x2 volume1; ae_f32x2 out_sample0; @@ -348,11 +320,9 @@ static void vol_s16_to_s16(struct processing_module *mod, struct input_stream_bu ae_f32x2 *vol; ae_valign inu; ae_valign outu = AE_ZALIGN64(); - ae_f16x4 *in = (ae_f16x4 *)audio_stream_wrap(source, (char *)audio_stream_get_rptr(source) - + bsource->consumed); - ae_f16x4 *out = (ae_f16x4 *)audio_stream_wrap(sink, (char *)audio_stream_get_wptr(sink) - + bsink->size); - const int channels_count = audio_stream_get_channels(sink); + const ae_f16x4 *in = source->ptr; + ae_f16x4 *out = sink->ptr; + const int channels_count = cd->channels; const int inc = sizeof(ae_f32x2); int samples = channels_count * frames; @@ -372,9 +342,9 @@ static void vol_s16_to_s16(struct processing_module *mod, struct input_stream_bu AE_SETCEND0(buf_end); while (samples) { - m = audio_stream_samples_without_wrap_s16(source, in); + m = cir_buf_samples_without_wrap_s16(in, source->buf_end); n = MIN(m, samples); - m = audio_stream_samples_without_wrap_s16(sink, out); + m = cir_buf_samples_without_wrap_s16(out, sink->buf_end); n = MIN(m, n); inu = AE_LA64_PP(in); for (i = 0; i < n; i += 4) { @@ -414,10 +384,8 @@ static void vol_s16_to_s16(struct processing_module *mod, struct input_stream_bu } AE_SA64POS_FP(outu, out); samples -= n; - bsource->consumed += VOL_S16_SAMPLES_TO_BYTES(n); - bsink->size += VOL_S16_SAMPLES_TO_BYTES(n); - in = audio_stream_wrap(source, in); - out = audio_stream_wrap(sink, out); + in = cir_buf_wrap(in, source->buf_start, source->buf_end); + out = cir_buf_wrap(out, sink->buf_start, sink->buf_end); } } @@ -430,27 +398,24 @@ static void vol_s16_to_s16(struct processing_module *mod, struct input_stream_bu * \param[in] attenuation factor for peakmeter adjustment (unused) */ static void vol_passthrough_s16_to_s16(struct processing_module *mod, - struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, + struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; + struct vol_data *cd = module_get_private_data(mod); ae_f16x4 in_sample; int i, n, m; ae_valign inu; ae_valign outu = AE_ZALIGN64(); - ae_f16x4 *in = (ae_f16x4 *)audio_stream_wrap(source, (char *)audio_stream_get_rptr(source) - + bsource->consumed); - ae_f16x4 *out = (ae_f16x4 *)audio_stream_wrap(sink, (char *)audio_stream_get_wptr(sink) - + bsink->size); - const int channels_count = audio_stream_get_channels(sink); + const ae_f16x4 *in = source->ptr; + ae_f16x4 *out = sink->ptr; + const int channels_count = cd->channels; int samples = channels_count * frames; while (samples) { - m = audio_stream_samples_without_wrap_s16(source, in); + m = cir_buf_samples_without_wrap_s16(in, source->buf_end); n = MIN(m, samples); - m = audio_stream_samples_without_wrap_s16(sink, out); + m = cir_buf_samples_without_wrap_s16(out, sink->buf_end); n = MIN(m, n); inu = AE_LA64_PP(in); for (i = 0; i < n; i += 4) { @@ -460,10 +425,8 @@ static void vol_passthrough_s16_to_s16(struct processing_module *mod, } AE_SA64POS_FP(outu, out); samples -= n; - bsource->consumed += VOL_S16_SAMPLES_TO_BYTES(n); - bsink->size += VOL_S16_SAMPLES_TO_BYTES(n); - in = audio_stream_wrap(source, in); - out = audio_stream_wrap(sink, out); + in = cir_buf_wrap(in, source->buf_start, source->buf_end); + out = cir_buf_wrap(out, sink->buf_start, sink->buf_end); } } #endif /* CONFIG_FORMAT_S16LE */ diff --git a/src/audio/volume/volume_hifi3_with_peakvol.c b/src/audio/volume/volume_hifi3_with_peakvol.c index 4f89330baca7..bb38fc9bf7ef 100644 --- a/src/audio/volume/volume_hifi3_with_peakvol.c +++ b/src/audio/volume/volume_hifi3_with_peakvol.c @@ -36,34 +36,28 @@ LOG_MODULE_DECLARE(volume_hifi3, CONFIG_SOF_LOG_LEVEL); * \param[in] frames Number of frames to process. * \param[in] attenuation factor for peakmeter adjustment */ -static void vol_s24_to_s24_s32(struct processing_module *mod, struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, +static void vol_s24_to_s24_s32(struct processing_module *mod, struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { struct vol_data *cd = module_get_private_data(mod); - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; ae_f32x2 in_sample; ae_f32x2 out_sample; ae_f32x2 volume; int channel, n, i, m; - ae_f32 *in0 = (ae_f32 *)audio_stream_wrap(source, (char *)audio_stream_get_rptr(source) - + bsource->consumed); - ae_f32 *out0 = (ae_f32 *)audio_stream_wrap(sink, (char *)audio_stream_get_wptr(sink) - + bsink->size); + ae_f32 *in0 = (void *)source->ptr; + ae_f32 *out0 = sink->ptr; ae_f32 *in, *out; - const int channels_count = audio_stream_get_channels(sink); + const int channels_count = cd->channels; const int inc = sizeof(ae_f32) * channels_count; int samples = channels_count * frames; ae_f32x2 peak_vol; uint32_t *peak_meter = cd->peak_regs.peak_meter; - bsource->consumed += VOL_S32_SAMPLES_TO_BYTES(samples); - bsink->size += VOL_S32_SAMPLES_TO_BYTES(samples); while (samples) { - m = audio_stream_samples_without_wrap_s32(source, in0); + m = cir_buf_samples_without_wrap_s32(in0, source->buf_end); n = MIN(m, samples); - m = audio_stream_samples_without_wrap_s32(sink, out0); + m = cir_buf_samples_without_wrap_s32(out0, sink->buf_end); n = MIN(m, n); for (channel = 0; channel < channels_count; channel++) { peak_vol = AE_ZERO32(); @@ -100,8 +94,8 @@ static void vol_s24_to_s24_s32(struct processing_module *mod, struct input_strea peak_meter[channel] = AE_MAX32(peak_vol, peak_meter[channel]); } samples -= n; - out0 = audio_stream_wrap(sink, out0 + n); - in0 = audio_stream_wrap(source, in0 + n); + out0 = cir_buf_wrap(out0 + n, sink->buf_start, sink->buf_end); + in0 = cir_buf_wrap(in0 + n, source->buf_start, source->buf_end); } } @@ -114,32 +108,26 @@ static void vol_s24_to_s24_s32(struct processing_module *mod, struct input_strea * \param[in] attenuation factor for peakmeter adjustment */ static void vol_passthrough_s24_to_s24_s32(struct processing_module *mod, - struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, + struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { struct vol_data *cd = module_get_private_data(mod); - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; ae_f32x2 in_sample; int channel, n, i, m; - ae_f32 *in0 = (ae_f32 *)audio_stream_wrap(source, (char *)audio_stream_get_rptr(source) - + bsource->consumed); - ae_f32 *out0 = (ae_f32 *)audio_stream_wrap(sink, (char *)audio_stream_get_wptr(sink) - + bsink->size); + ae_f32 *in0 = (void *)source->ptr; + ae_f32 *out0 = sink->ptr; ae_f32 *in, *out; - const int channels_count = audio_stream_get_channels(sink); + const int channels_count = cd->channels; const int inc = sizeof(ae_f32) * channels_count; int samples = channels_count * frames; ae_f32x2 peak_vol; uint32_t *peak_meter = cd->peak_regs.peak_meter; - bsource->consumed += VOL_S32_SAMPLES_TO_BYTES(samples); - bsink->size += VOL_S32_SAMPLES_TO_BYTES(samples); while (samples) { - m = audio_stream_samples_without_wrap_s32(source, in0); + m = cir_buf_samples_without_wrap_s32(in0, source->buf_end); n = MIN(m, samples); - m = audio_stream_samples_without_wrap_s32(sink, out0); + m = cir_buf_samples_without_wrap_s32(out0, sink->buf_end); n = MIN(m, n); for (channel = 0; channel < channels_count; channel++) { peak_vol = AE_ZERO32(); @@ -159,8 +147,8 @@ static void vol_passthrough_s24_to_s24_s32(struct processing_module *mod, peak_meter[channel] = AE_MAX32(peak_vol, peak_meter[channel]); } samples -= n; - out0 = audio_stream_wrap(sink, out0 + n); - in0 = audio_stream_wrap(source, in0 + n); + out0 = cir_buf_wrap(out0 + n, sink->buf_start, sink->buf_end); + in0 = cir_buf_wrap(in0 + n, source->buf_start, source->buf_end); } } #endif /* CONFIG_FORMAT_S24LE */ @@ -174,34 +162,28 @@ static void vol_passthrough_s24_to_s24_s32(struct processing_module *mod, * \param[in] frames Number of frames to process. * \param[in] attenuation factor for peakmeter adjustment */ -static void vol_s32_to_s24_s32(struct processing_module *mod, struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, +static void vol_s32_to_s24_s32(struct processing_module *mod, struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { struct vol_data *cd = module_get_private_data(mod); - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; ae_f32x2 in_sample; ae_f32x2 out_sample; ae_f32x2 volume; int i, n, channel, m; - const int channels_count = audio_stream_get_channels(sink); + const int channels_count = cd->channels; const int inc = sizeof(ae_f32) * channels_count; int samples = channels_count * frames; - ae_f32 *in0 = (ae_f32 *)audio_stream_wrap(source, (char *)audio_stream_get_rptr(source) - + bsource->consumed); - ae_f32 *out0 = (ae_f32 *)audio_stream_wrap(sink, (char *)audio_stream_get_wptr(sink) - + bsink->size); + ae_f32 *in0 = (void *)source->ptr; + ae_f32 *out0 = sink->ptr; ae_f32 *in, *out; ae_f32x2 peak_vol; uint32_t *peak_meter = cd->peak_regs.peak_meter; - bsource->consumed += VOL_S32_SAMPLES_TO_BYTES(samples); - bsink->size += VOL_S32_SAMPLES_TO_BYTES(samples); while (samples) { - m = audio_stream_samples_without_wrap_s32(source, in0); + m = cir_buf_samples_without_wrap_s32(in0, source->buf_end); n = MIN(m, samples); - m = audio_stream_samples_without_wrap_s32(sink, out0); + m = cir_buf_samples_without_wrap_s32(out0, sink->buf_end); n = MIN(m, n); for (channel = 0; channel < channels_count; channel++) { peak_vol = AE_ZERO32(); @@ -236,8 +218,8 @@ static void vol_s32_to_s24_s32(struct processing_module *mod, struct input_strea peak_meter[channel] = AE_MAX32(peak_vol, peak_meter[channel]); } samples -= n; - out0 = audio_stream_wrap(sink, out0 + n); - in0 = audio_stream_wrap(source, in0 + n); + out0 = cir_buf_wrap(out0 + n, sink->buf_start, sink->buf_end); + in0 = cir_buf_wrap(in0 + n, source->buf_start, source->buf_end); } } @@ -250,32 +232,26 @@ static void vol_s32_to_s24_s32(struct processing_module *mod, struct input_strea * \param[in] attenuation factor for peakmeter adjustment */ static void vol_passthrough_s32_to_s24_s32(struct processing_module *mod, - struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, + struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { struct vol_data *cd = module_get_private_data(mod); - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; ae_f32x2 in_sample; int i, n, channel, m; - const int channels_count = audio_stream_get_channels(sink); + const int channels_count = cd->channels; const int inc = sizeof(ae_f32) * channels_count; int samples = channels_count * frames; - ae_f32 *in0 = (ae_f32 *)audio_stream_wrap(source, (char *)audio_stream_get_rptr(source) - + bsource->consumed); - ae_f32 *out0 = (ae_f32 *)audio_stream_wrap(sink, (char *)audio_stream_get_wptr(sink) - + bsink->size); + ae_f32 *in0 = (void *)source->ptr; + ae_f32 *out0 = sink->ptr; ae_f32 *in, *out; ae_f32x2 peak_vol; uint32_t *peak_meter = cd->peak_regs.peak_meter; - bsource->consumed += VOL_S32_SAMPLES_TO_BYTES(samples); - bsink->size += VOL_S32_SAMPLES_TO_BYTES(samples); while (samples) { - m = audio_stream_samples_without_wrap_s32(source, in0); + m = cir_buf_samples_without_wrap_s32(in0, source->buf_end); n = MIN(m, samples); - m = audio_stream_samples_without_wrap_s32(sink, out0); + m = cir_buf_samples_without_wrap_s32(out0, sink->buf_end); n = MIN(m, n); for (channel = 0; channel < channels_count; channel++) { peak_vol = AE_ZERO32(); @@ -295,8 +271,8 @@ static void vol_passthrough_s32_to_s24_s32(struct processing_module *mod, peak_meter[channel] = AE_MAX32(peak_vol, peak_meter[channel]); } samples -= n; - out0 = audio_stream_wrap(sink, out0 + n); - in0 = audio_stream_wrap(source, in0 + n); + out0 = cir_buf_wrap(out0 + n, sink->buf_start, sink->buf_end); + in0 = cir_buf_wrap(in0 + n, source->buf_start, source->buf_end); } } #endif /* CONFIG_FORMAT_S32LE */ @@ -310,13 +286,11 @@ static void vol_passthrough_s32_to_s24_s32(struct processing_module *mod, * \param[in] frames Number of frames to process. * \param[in] attenuation factor for peakmeter adjustment (unused for 16bit) */ -static void vol_s16_to_s16(struct processing_module *mod, struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, +static void vol_s16_to_s16(struct processing_module *mod, struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { struct vol_data *cd = module_get_private_data(mod); - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; ae_f32x2 volume; ae_f32x2 out_sample0; ae_f16x4 in_sample; @@ -324,20 +298,18 @@ static void vol_s16_to_s16(struct processing_module *mod, struct input_stream_bu int i, n, channel, m; ae_f16 *in; ae_f16 *out; - ae_f16 *in0 = (ae_f16 *)audio_stream_wrap(source, (char *)audio_stream_get_rptr(source) - + bsource->consumed); - ae_f16 *out0 = (ae_f16 *)audio_stream_wrap(sink, (char *)audio_stream_get_wptr(sink) - + bsink->size); - const int channels_count = audio_stream_get_channels(sink); + ae_f16 *in0 = (void *)source->ptr; + ae_f16 *out0 = sink->ptr; + const int channels_count = cd->channels; const int inc = sizeof(ae_f16) * channels_count; int samples = channels_count * frames; ae_f32x2 peak_vol; uint32_t *peak_meter = cd->peak_regs.peak_meter; while (samples) { - m = audio_stream_samples_without_wrap_s16(source, in0); + m = cir_buf_samples_without_wrap_s16(in0, source->buf_end); n = MIN(m, samples); - m = audio_stream_samples_without_wrap_s16(sink, out0); + m = cir_buf_samples_without_wrap_s16(out0, sink->buf_end); n = MIN(m, n); for (channel = 0; channel < channels_count; channel++) { peak_vol = AE_ZERO32(); @@ -377,11 +349,9 @@ static void vol_s16_to_s16(struct processing_module *mod, struct input_stream_bu peak_vol = AE_SLAA32(peak_vol, PEAK_16S_32C_ADJUST); peak_meter[channel] = AE_MAX32(peak_vol, peak_meter[channel]); } - out0 = audio_stream_wrap(sink, out0 + n); - in0 = audio_stream_wrap(source, in0 + n); + out0 = cir_buf_wrap(out0 + n, sink->buf_start, sink->buf_end); + in0 = cir_buf_wrap(in0 + n, source->buf_start, source->buf_end); samples -= n; - bsource->consumed += VOL_S16_SAMPLES_TO_BYTES(n); - bsink->size += VOL_S16_SAMPLES_TO_BYTES(n); } } @@ -394,31 +364,27 @@ static void vol_s16_to_s16(struct processing_module *mod, struct input_stream_bu * \param[in] attenuation factor for peakmeter adjustment (unused for 16bit) */ static void vol_passthrough_s16_to_s16(struct processing_module *mod, - struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, + struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { struct vol_data *cd = module_get_private_data(mod); - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; ae_f16x4 in_sample; int i, n, channel, m; ae_f16 *in; ae_f16 *out; - ae_f16 *in0 = (ae_f16 *)audio_stream_wrap(source, (char *)audio_stream_get_rptr(source) - + bsource->consumed); - ae_f16 *out0 = (ae_f16 *)audio_stream_wrap(sink, (char *)audio_stream_get_wptr(sink) - + bsink->size); - const int channels_count = audio_stream_get_channels(sink); + ae_f16 *in0 = (void *)source->ptr; + ae_f16 *out0 = sink->ptr; + const int channels_count = cd->channels; const int inc = sizeof(ae_f16) * channels_count; int samples = channels_count * frames; ae_f32x2 peak_vol; uint32_t *peak_meter = cd->peak_regs.peak_meter; while (samples) { - m = audio_stream_samples_without_wrap_s16(source, in0); + m = cir_buf_samples_without_wrap_s16(in0, source->buf_end); n = MIN(m, samples); - m = audio_stream_samples_without_wrap_s16(sink, out0); + m = cir_buf_samples_without_wrap_s16(out0, sink->buf_end); n = MIN(m, n); for (channel = 0; channel < channels_count; channel++) { peak_vol = AE_ZERO32(); @@ -439,11 +405,9 @@ static void vol_passthrough_s16_to_s16(struct processing_module *mod, peak_vol = AE_SLAA32(peak_vol, PEAK_16S_32C_ADJUST); peak_meter[channel] = AE_MAX32(peak_vol, peak_meter[channel]); } - out0 = audio_stream_wrap(sink, out0 + n); - in0 = audio_stream_wrap(source, in0 + n); + out0 = cir_buf_wrap(out0 + n, sink->buf_start, sink->buf_end); + in0 = cir_buf_wrap(in0 + n, source->buf_start, source->buf_end); samples -= n; - bsource->consumed += VOL_S16_SAMPLES_TO_BYTES(n); - bsink->size += VOL_S16_SAMPLES_TO_BYTES(n); } } #endif /* CONFIG_FORMAT_S16LE */ diff --git a/src/audio/volume/volume_hifi4.c b/src/audio/volume/volume_hifi4.c index 2f879a31ec0e..44d88eda586b 100644 --- a/src/audio/volume/volume_hifi4.c +++ b/src/audio/volume/volume_hifi4.c @@ -56,13 +56,11 @@ static void vol_store_gain(struct vol_data *cd, const int channels_count) * \param[in] frames Number of frames to process. * \param[in] attenuation factor for peakmeter adjustment (unused) */ -static void vol_s24_to_s24_s32(struct processing_module *mod, struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, +static void vol_s24_to_s24_s32(struct processing_module *mod, struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { struct vol_data *cd = module_get_private_data(mod); - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; ae_f32x2 in_sample; ae_f32x2 out_sample; ae_f32x2 volume; @@ -72,11 +70,9 @@ static void vol_s24_to_s24_s32(struct processing_module *mod, struct input_strea ae_f32x2 *vol; ae_valign inu; ae_valign outu = AE_ZALIGN64(); - ae_f32x2 *in = (ae_f32x2 *)audio_stream_wrap(source, (char *)audio_stream_get_rptr(source) - + bsource->consumed); - ae_f32x2 *out = (ae_f32x2 *)audio_stream_wrap(sink, (char *)audio_stream_get_wptr(sink) - + bsink->size); - const int channels_count = audio_stream_get_channels(sink); + const ae_f32x2 *in = source->ptr; + ae_f32x2 *out = sink->ptr; + const int channels_count = cd->channels; const int inc = sizeof(ae_f32x2); int samples = channels_count * frames; @@ -94,13 +90,10 @@ static void vol_s24_to_s24_s32(struct processing_module *mod, struct input_strea AE_SETCBEGIN0(buf); AE_SETCEND0(buf_end); - bsource->consumed += VOL_S32_SAMPLES_TO_BYTES(samples); - bsink->size += VOL_S32_SAMPLES_TO_BYTES(samples); - while (samples) { - m = audio_stream_samples_without_wrap_s24(source, in); + m = cir_buf_samples_without_wrap_s32(in, source->buf_end); n = MIN(m, samples); - m = audio_stream_samples_without_wrap_s24(sink, out); + m = cir_buf_samples_without_wrap_s32(out, sink->buf_end); n = MIN(m, n); inu = AE_LA64_PP(in); /* process two continuous sample data once */ @@ -130,8 +123,8 @@ static void vol_s24_to_s24_s32(struct processing_module *mod, struct input_strea } AE_SA64POS_FP(outu, out); samples -= n; - in = audio_stream_wrap(source, in); - out = audio_stream_wrap(sink, out); + in = cir_buf_wrap(in, source->buf_start, source->buf_end); + out = cir_buf_wrap(out, sink->buf_start, sink->buf_end); } } @@ -144,29 +137,23 @@ static void vol_s24_to_s24_s32(struct processing_module *mod, struct input_strea * \param[in] attenuation factor for peakmeter adjustment (unused) */ static void vol_passthrough_s24_to_s24_s32(struct processing_module *mod, - struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, + struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; + struct vol_data *cd = module_get_private_data(mod); ae_f32x2 in_sample; int i, n, m; ae_valign inu; ae_valign outu = AE_ZALIGN64(); - ae_f32x2 *in = (ae_f32x2 *)audio_stream_wrap(source, (char *)audio_stream_get_rptr(source) - + bsource->consumed); - ae_f32x2 *out = (ae_f32x2 *)audio_stream_wrap(sink, (char *)audio_stream_get_wptr(sink) - + bsink->size); - int samples = audio_stream_get_channels(sink) * frames; - - bsource->consumed += VOL_S32_SAMPLES_TO_BYTES(samples); - bsink->size += VOL_S32_SAMPLES_TO_BYTES(samples); + const ae_f32x2 *in = source->ptr; + ae_f32x2 *out = sink->ptr; + int samples = cd->channels * frames; while (samples) { - m = audio_stream_samples_without_wrap_s24(source, in); + m = cir_buf_samples_without_wrap_s32(in, source->buf_end); n = MIN(m, samples); - m = audio_stream_samples_without_wrap_s24(sink, out); + m = cir_buf_samples_without_wrap_s32(out, sink->buf_end); n = MIN(m, n); inu = AE_LA64_PP(in); /* process two continuous sample data once */ @@ -178,8 +165,8 @@ static void vol_passthrough_s24_to_s24_s32(struct processing_module *mod, } AE_SA64POS_FP(outu, out); samples -= n; - in = audio_stream_wrap(source, in); - out = audio_stream_wrap(sink, out); + in = cir_buf_wrap(in, source->buf_start, source->buf_end); + out = cir_buf_wrap(out, sink->buf_start, sink->buf_end); } } @@ -194,13 +181,11 @@ static void vol_passthrough_s24_to_s24_s32(struct processing_module *mod, * \param[in] frames Number of frames to process. * \param[in] attenuation factor for peakmeter adjustment (unused) */ -static void vol_s32_to_s24_s32(struct processing_module *mod, struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, +static void vol_s32_to_s24_s32(struct processing_module *mod, struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { struct vol_data *cd = module_get_private_data(mod); - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; ae_f32x2 in_sample; ae_f32x2 out_sample; ae_f32x2 volume; @@ -210,13 +195,11 @@ static void vol_s32_to_s24_s32(struct processing_module *mod, struct input_strea ae_f32x2 *vol; ae_valign inu; ae_valign outu = AE_ZALIGN64(); - const int channels_count = audio_stream_get_channels(sink); + const int channels_count = cd->channels; const int inc = sizeof(ae_f32x2); int samples = channels_count * frames; - ae_f32x2 *in = (ae_f32x2 *)audio_stream_wrap(source, (char *)audio_stream_get_rptr(source) - + bsource->consumed); - ae_f32x2 *out = (ae_f32x2 *)audio_stream_wrap(sink, (char *)audio_stream_get_wptr(sink) - + bsink->size); + const ae_f32x2 *in = source->ptr; + ae_f32x2 *out = sink->ptr; /* to ensure the address is 8-byte aligned and avoid risk of * error loading of volume gain while the cd->vol would be set @@ -232,13 +215,10 @@ static void vol_s32_to_s24_s32(struct processing_module *mod, struct input_strea AE_SETCBEGIN0(buf); AE_SETCEND0(buf_end); - bsource->consumed += VOL_S32_SAMPLES_TO_BYTES(samples); - bsink->size += VOL_S32_SAMPLES_TO_BYTES(samples); - while (samples) { - m = audio_stream_samples_without_wrap_s32(source, in); + m = cir_buf_samples_without_wrap_s32(in, source->buf_end); n = MIN(m, samples); - m = audio_stream_samples_without_wrap_s32(sink, out); + m = cir_buf_samples_without_wrap_s32(out, sink->buf_end); n = MIN(m, n); inu = AE_LA64_PP(in); /* process two continuous sample data once */ @@ -268,8 +248,8 @@ static void vol_s32_to_s24_s32(struct processing_module *mod, struct input_strea } AE_SA64POS_FP(outu, out); samples -= n; - in = audio_stream_wrap(source, in); - out = audio_stream_wrap(sink, out); + in = cir_buf_wrap(in, source->buf_start, source->buf_end); + out = cir_buf_wrap(out, sink->buf_start, sink->buf_end); } } @@ -282,29 +262,24 @@ static void vol_s32_to_s24_s32(struct processing_module *mod, struct input_strea * \param[in] attenuation factor for peakmeter adjustment (unused) */ static void vol_passthrough_s32_to_s24_s32(struct processing_module *mod, - struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, + struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; + struct vol_data *cd = module_get_private_data(mod); ae_f32x2 in_sample; int i, n, m; ae_valign inu; ae_valign outu = AE_ZALIGN64(); - const int channels_count = audio_stream_get_channels(sink); + const int channels_count = cd->channels; int samples = channels_count * frames; - ae_f32x2 *in = (ae_f32x2 *)audio_stream_wrap(source, (char *)audio_stream_get_rptr(source) - + bsource->consumed); - ae_f32x2 *out = (ae_f32x2 *)audio_stream_wrap(sink, (char *)audio_stream_get_wptr(sink) - + bsink->size); + const ae_f32x2 *in = source->ptr; + ae_f32x2 *out = sink->ptr; - bsource->consumed += VOL_S32_SAMPLES_TO_BYTES(samples); - bsink->size += VOL_S32_SAMPLES_TO_BYTES(samples); while (samples) { - m = audio_stream_samples_without_wrap_s32(source, in); + m = cir_buf_samples_without_wrap_s32(in, source->buf_end); n = MIN(m, samples); - m = audio_stream_samples_without_wrap_s32(sink, out); + m = cir_buf_samples_without_wrap_s32(out, sink->buf_end); n = MIN(m, n); inu = AE_LA64_PP(in); /* process two continuous sample data once */ @@ -315,8 +290,8 @@ static void vol_passthrough_s32_to_s24_s32(struct processing_module *mod, } AE_SA64POS_FP(outu, out); samples -= n; - in = audio_stream_wrap(source, in); - out = audio_stream_wrap(sink, out); + in = cir_buf_wrap(in, source->buf_start, source->buf_end); + out = cir_buf_wrap(out, sink->buf_start, sink->buf_end); } } #endif /* CONFIG_FORMAT_S32LE */ @@ -330,13 +305,11 @@ static void vol_passthrough_s32_to_s24_s32(struct processing_module *mod, * \param[in] frames Number of frames to process. * \param[in] attenuation factor for peakmeter adjustment (unused) */ -static void vol_s16_to_s16(struct processing_module *mod, struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, +static void vol_s16_to_s16(struct processing_module *mod, struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { struct vol_data *cd = module_get_private_data(mod); - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; ae_f32x2 volume0; ae_f32x2 volume1; ae_f32x2 out_sample0; @@ -349,11 +322,9 @@ static void vol_s16_to_s16(struct processing_module *mod, struct input_stream_bu ae_f32x2 *vol; ae_valign inu; ae_valign outu = AE_ZALIGN64(); - ae_f16x4 *in = (ae_f16x4 *)audio_stream_wrap(source, (char *)audio_stream_get_rptr(source) - + bsource->consumed); - ae_f16x4 *out = (ae_f16x4 *)audio_stream_wrap(sink, (char *)audio_stream_get_wptr(sink) - + bsink->size); - const int channels_count = audio_stream_get_channels(sink); + const ae_f16x4 *in = source->ptr; + ae_f16x4 *out = sink->ptr; + const int channels_count = cd->channels; const int inc = sizeof(ae_f32x2); int samples = channels_count * frames; @@ -373,9 +344,9 @@ static void vol_s16_to_s16(struct processing_module *mod, struct input_stream_bu AE_SETCEND0(buf_end); while (samples) { - m = audio_stream_samples_without_wrap_s16(source, in); + m = cir_buf_samples_without_wrap_s16(in, source->buf_end); n = MIN(m, samples); - m = audio_stream_samples_without_wrap_s16(sink, out); + m = cir_buf_samples_without_wrap_s16(out, sink->buf_end); n = MIN(m, n); inu = AE_LA64_PP(in); m = n >> 2; @@ -442,10 +413,8 @@ static void vol_s16_to_s16(struct processing_module *mod, struct input_stream_bu } samples -= n; - bsource->consumed += VOL_S16_SAMPLES_TO_BYTES(n); - bsink->size += VOL_S16_SAMPLES_TO_BYTES(n); - in = audio_stream_wrap(source, in); - out = audio_stream_wrap(sink, out); + in = cir_buf_wrap(in, source->buf_start, source->buf_end); + out = cir_buf_wrap(out, sink->buf_start, sink->buf_end); } } @@ -458,29 +427,24 @@ static void vol_s16_to_s16(struct processing_module *mod, struct input_stream_bu * \param[in] attenuation factor for peakmeter adjustment (unused) */ static void vol_passthrough_s16_to_s16(struct processing_module *mod, - struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, + struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; + struct vol_data *cd = module_get_private_data(mod); ae_f16x4 in_sample; int i, n, m, left; ae_valign inu; ae_valign outu = AE_ZALIGN64(); - ae_f16x4 *in = (ae_f16x4 *)audio_stream_wrap(source, (char *)audio_stream_get_rptr(source) - + bsource->consumed); - ae_f16x4 *out = (ae_f16x4 *)audio_stream_wrap(sink, (char *)audio_stream_get_wptr(sink) - + bsink->size); - const int channels_count = audio_stream_get_channels(sink); + const ae_f16x4 *in = source->ptr; + ae_f16x4 *out = sink->ptr; + const int channels_count = cd->channels; int samples = channels_count * frames; - bsource->consumed += VOL_S16_SAMPLES_TO_BYTES(samples); - bsink->size += VOL_S16_SAMPLES_TO_BYTES(samples); while (samples) { - m = audio_stream_samples_without_wrap_s16(source, in); + m = cir_buf_samples_without_wrap_s16(in, source->buf_end); n = MIN(m, samples); - m = audio_stream_samples_without_wrap_s16(sink, out); + m = cir_buf_samples_without_wrap_s16(out, sink->buf_end); n = MIN(m, n); inu = AE_LA64_PP(in); m = n >> 2; @@ -501,8 +465,8 @@ static void vol_passthrough_s16_to_s16(struct processing_module *mod, } samples -= n; - in = audio_stream_wrap(source, in); - out = audio_stream_wrap(sink, out); + in = cir_buf_wrap(in, source->buf_start, source->buf_end); + out = cir_buf_wrap(out, sink->buf_start, sink->buf_end); } } #endif /* CONFIG_FORMAT_S16LE */ diff --git a/src/audio/volume/volume_hifi4_with_peakvol.c b/src/audio/volume/volume_hifi4_with_peakvol.c index f3908ee1a8a0..a1d0ed70a579 100644 --- a/src/audio/volume/volume_hifi4_with_peakvol.c +++ b/src/audio/volume/volume_hifi4_with_peakvol.c @@ -49,13 +49,11 @@ static inline void vol_store_gain(struct vol_data *cd, const int channels_count) * \param[in] frames Number of frames to process. * \param[in] attenuation factor for peakmeter adjustment */ -static void vol_s24_to_s24_s32(struct processing_module *mod, struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, +static void vol_s24_to_s24_s32(struct processing_module *mod, struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { struct vol_data *cd = module_get_private_data(mod); - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; ae_f32x2 in_sample; ae_f32x2 out_sample; ae_f32x2 volume; @@ -63,11 +61,9 @@ static void vol_s24_to_s24_s32(struct processing_module *mod, struct input_strea ae_f32x2 *vol; ae_valign inu; ae_valign outu = AE_ZALIGN64(); - ae_f32x2 *in = (ae_f32x2 *)audio_stream_wrap(source, (char *)audio_stream_get_rptr(source) - + bsource->consumed); - ae_f32x2 *out = (ae_f32x2 *)audio_stream_wrap(sink, (char *)audio_stream_get_wptr(sink) - + bsink->size); - const int channels_count = audio_stream_get_channels(sink); + const ae_f32x2 *in = source->ptr; + ae_f32x2 *out = sink->ptr; + const int channels_count = cd->channels; const int inc = sizeof(ae_f32x2); int samples = channels_count * frames; ae_f32x2 temp; @@ -89,13 +85,10 @@ static void vol_s24_to_s24_s32(struct processing_module *mod, struct input_strea AE_SETCBEGIN0(vol); AE_SETCEND0(cd->vol + channels_count * 2); - bsource->consumed += VOL_S32_SAMPLES_TO_BYTES(samples); - bsink->size += VOL_S32_SAMPLES_TO_BYTES(samples); - while (samples) { - m = audio_stream_samples_without_wrap_s32(source, in); + m = cir_buf_samples_without_wrap_s32(in, source->buf_end); n = MIN(m, samples); - m = audio_stream_samples_without_wrap_s16(sink, out); + m = cir_buf_samples_without_wrap_s16(out, sink->buf_end); n = MIN(m, n); inu = AE_LA64_PP(in); /* process two continuous sample data once */ @@ -128,8 +121,8 @@ static void vol_s24_to_s24_s32(struct processing_module *mod, struct input_strea } AE_SA64POS_FP(outu, out); samples -= n; - in = audio_stream_wrap(source, in); - out = audio_stream_wrap(sink, out); + in = cir_buf_wrap(in, source->buf_start, source->buf_end); + out = cir_buf_wrap(out, sink->buf_start, sink->buf_end); } for (i = 0; i < channels_count; i++) cd->peak_regs.peak_meter[i] = MAX(cd->peak_vol[i], @@ -146,23 +139,19 @@ static void vol_s24_to_s24_s32(struct processing_module *mod, struct input_strea * \param[in] attenuation factor for peakmeter adjustment */ static void vol_passthrough_s24_to_s24_s32(struct processing_module *mod, - struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, + struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { struct vol_data *cd = module_get_private_data(mod); - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; ae_f32x2 in_sample; int i, n, m; ae_valign inu; ae_valign outu = AE_ZALIGN64(); - ae_f32x2 *in = (ae_f32x2 *)audio_stream_wrap(source, (char *)audio_stream_get_rptr(source) - + bsource->consumed); - ae_f32x2 *out = (ae_f32x2 *)audio_stream_wrap(sink, (char *)audio_stream_get_wptr(sink) - + bsink->size); - const int channels_count = audio_stream_get_channels(sink); + const ae_f32x2 *in = source->ptr; + ae_f32x2 *out = sink->ptr; + const int channels_count = cd->channels; const int inc = sizeof(ae_f32x2); int samples = channels_count * frames; ae_f32x2 temp; @@ -172,13 +161,10 @@ static void vol_passthrough_s24_to_s24_s32(struct processing_module *mod, AE_SETCBEGIN1(cd->peak_vol); AE_SETCEND1(cd->peak_vol + channels_count * 2); - bsource->consumed += VOL_S32_SAMPLES_TO_BYTES(samples); - bsink->size += VOL_S32_SAMPLES_TO_BYTES(samples); - while (samples) { - m = audio_stream_samples_without_wrap_s32(source, in); + m = cir_buf_samples_without_wrap_s32(in, source->buf_end); n = MIN(m, samples); - m = audio_stream_samples_without_wrap_s16(sink, out); + m = cir_buf_samples_without_wrap_s16(out, sink->buf_end); n = MIN(m, n); inu = AE_LA64_PP(in); /* process two continuous sample data once */ @@ -194,8 +180,8 @@ static void vol_passthrough_s24_to_s24_s32(struct processing_module *mod, } AE_SA64POS_FP(outu, out); samples -= n; - in = audio_stream_wrap(source, in); - out = audio_stream_wrap(sink, out); + in = cir_buf_wrap(in, source->buf_start, source->buf_end); + out = cir_buf_wrap(out, sink->buf_start, sink->buf_end); } for (i = 0; i < channels_count; i++) cd->peak_regs.peak_meter[i] = MAX(cd->peak_vol[i], @@ -213,13 +199,11 @@ static void vol_passthrough_s24_to_s24_s32(struct processing_module *mod, * \param[in] frames Number of frames to process. * \param[in] attenuation factor for peakmeter adjustment */ -static void vol_s32_to_s24_s32(struct processing_module *mod, struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, +static void vol_s32_to_s24_s32(struct processing_module *mod, struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { struct vol_data *cd = module_get_private_data(mod); - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; ae_f32x2 in_sample; ae_f32x2 out_sample; ae_f32x2 volume; @@ -229,13 +213,11 @@ static void vol_s32_to_s24_s32(struct processing_module *mod, struct input_strea ae_f32x2 *vol; ae_valign inu; ae_valign outu = AE_ZALIGN64(); - const int channels_count = audio_stream_get_channels(sink); + const int channels_count = cd->channels; const int inc = sizeof(ae_f32x2); int samples = channels_count * frames; - ae_f32x2 *in = (ae_f32x2 *)audio_stream_wrap(source, (char *)audio_stream_get_rptr(source) - + bsource->consumed); - ae_f32x2 *out = (ae_f32x2 *)audio_stream_wrap(sink, (char *)audio_stream_get_wptr(sink) - + bsink->size); + const ae_f32x2 *in = source->ptr; + ae_f32x2 *out = sink->ptr; ae_f32x2 temp; ae_f32x2 *peakvol = (ae_f32x2 *)cd->peak_vol; @@ -257,13 +239,10 @@ static void vol_s32_to_s24_s32(struct processing_module *mod, struct input_strea AE_SETCBEGIN0(buf); AE_SETCEND0(buf_end); - bsource->consumed += VOL_S32_SAMPLES_TO_BYTES(samples); - bsink->size += VOL_S32_SAMPLES_TO_BYTES(samples); - while (samples) { - m = audio_stream_samples_without_wrap_s32(source, in); + m = cir_buf_samples_without_wrap_s32(in, source->buf_end); n = MIN(m, samples); - m = audio_stream_samples_without_wrap_s32(sink, out); + m = cir_buf_samples_without_wrap_s32(out, sink->buf_end); n = MIN(m, n); inu = AE_LA64_PP(in); /* process two continuous sample data once */ @@ -297,8 +276,8 @@ static void vol_s32_to_s24_s32(struct processing_module *mod, struct input_strea } AE_SA64POS_FP(outu, out); samples -= n; - in = audio_stream_wrap(source, in); - out = audio_stream_wrap(sink, out); + in = cir_buf_wrap(in, source->buf_start, source->buf_end); + out = cir_buf_wrap(out, sink->buf_start, sink->buf_end); } for (i = 0; i < channels_count; i++) cd->peak_regs.peak_meter[i] = MAX(cd->peak_vol[i], @@ -314,37 +293,30 @@ static void vol_s32_to_s24_s32(struct processing_module *mod, struct input_strea * \param[in] attenuation factor for peakmeter adjustment */ static void vol_passthrough_s32_to_s24_s32(struct processing_module *mod, - struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, + struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { struct vol_data *cd = module_get_private_data(mod); - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; ae_f32x2 in_sample; int i, n, m; ae_valign inu; ae_valign outu = AE_ZALIGN64(); - const int channels_count = audio_stream_get_channels(sink); + const int channels_count = cd->channels; const int inc = sizeof(ae_f32x2); int samples = channels_count * frames; - ae_f32x2 *in = (ae_f32x2 *)audio_stream_wrap(source, (char *)audio_stream_get_rptr(source) - + bsource->consumed); - ae_f32x2 *out = (ae_f32x2 *)audio_stream_wrap(sink, (char *)audio_stream_get_wptr(sink) - + bsink->size); + const ae_f32x2 *in = source->ptr; + ae_f32x2 *out = sink->ptr; ae_f32x2 temp; ae_f32x2 *peakvol = (ae_f32x2 *)cd->peak_vol; /* Set peakvol(which stores the peak volume data twice) as circular buffer */ AE_SETCBEGIN1(cd->peak_vol); AE_SETCEND1(cd->peak_vol + channels_count * 2); - bsource->consumed += VOL_S32_SAMPLES_TO_BYTES(samples); - bsink->size += VOL_S32_SAMPLES_TO_BYTES(samples); - while (samples) { - m = audio_stream_samples_without_wrap_s32(source, in); + m = cir_buf_samples_without_wrap_s32(in, source->buf_end); n = MIN(m, samples); - m = audio_stream_samples_without_wrap_s32(sink, out); + m = cir_buf_samples_without_wrap_s32(out, sink->buf_end); n = MIN(m, n); inu = AE_LA64_PP(in); /* process two continuous sample data once */ @@ -360,8 +332,8 @@ static void vol_passthrough_s32_to_s24_s32(struct processing_module *mod, } AE_SA64POS_FP(outu, out); samples -= n; - in = audio_stream_wrap(source, in); - out = audio_stream_wrap(sink, out); + in = cir_buf_wrap(in, source->buf_start, source->buf_end); + out = cir_buf_wrap(out, sink->buf_start, sink->buf_end); } for (i = 0; i < channels_count; i++) cd->peak_regs.peak_meter[i] = MAX(cd->peak_vol[i], @@ -378,13 +350,11 @@ static void vol_passthrough_s32_to_s24_s32(struct processing_module *mod, * \param[in] frames Number of frames to process. * \param[in] attenuation factor for peakmeter adjustment (unused for 16bit) */ -static void vol_s16_to_s16(struct processing_module *mod, struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, +static void vol_s16_to_s16(struct processing_module *mod, struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { struct vol_data *cd = module_get_private_data(mod); - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; ae_f32x2 volume0; ae_f32x2 volume1; ae_f32x2 out_sample0; @@ -397,11 +367,9 @@ static void vol_s16_to_s16(struct processing_module *mod, struct input_stream_bu ae_f32x2 *vol; ae_valign inu; ae_valign outu = AE_ZALIGN64(); - ae_f16x4 *in = (ae_f16x4 *)audio_stream_wrap(source, (char *)audio_stream_get_rptr(source) - + bsource->consumed); - ae_f16x4 *out = (ae_f16x4 *)audio_stream_wrap(sink, (char *)audio_stream_get_wptr(sink) - + bsink->size); - const int channels_count = audio_stream_get_channels(sink); + const ae_f16x4 *in = source->ptr; + ae_f16x4 *out = sink->ptr; + const int channels_count = cd->channels; const int inc = sizeof(ae_f32x2); int samples = channels_count * frames; ae_f32x2 temp; @@ -427,9 +395,9 @@ static void vol_s16_to_s16(struct processing_module *mod, struct input_stream_bu AE_SETCEND0(buf_end); while (samples) { - m = audio_stream_samples_without_wrap_s16(source, in); + m = cir_buf_samples_without_wrap_s16(in, source->buf_end); n = MIN(m, samples); - m = audio_stream_samples_without_wrap_s16(sink, out); + m = cir_buf_samples_without_wrap_s16(out, sink->buf_end); n = MIN(m, n); inu = AE_LA64_PP(in); m = n >> 2; @@ -509,10 +477,8 @@ static void vol_s16_to_s16(struct processing_module *mod, struct input_stream_bu } samples -= n; - in = audio_stream_wrap(source, in); - out = audio_stream_wrap(sink, out); - bsource->consumed += VOL_S16_SAMPLES_TO_BYTES(n); - bsink->size += VOL_S16_SAMPLES_TO_BYTES(n); + in = cir_buf_wrap(in, source->buf_start, source->buf_end); + out = cir_buf_wrap(out, sink->buf_start, sink->buf_end); } for (i = 0; i < channels_count; i++) { m = MAX(cd->peak_vol[i], cd->peak_vol[i + channels_count]); @@ -531,22 +497,18 @@ static void vol_s16_to_s16(struct processing_module *mod, struct input_stream_bu * \param[in] attenuation factor for peakmeter adjustment (unused for 16bit) */ static void vol_passthrough_s16_to_s16(struct processing_module *mod, - struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, + struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { struct vol_data *cd = module_get_private_data(mod); - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; ae_f16x4 in_sample; int i, n, m, left; ae_valign inu; ae_valign outu = AE_ZALIGN64(); - ae_f16x4 *in = (ae_f16x4 *)audio_stream_wrap(source, (char *)audio_stream_get_rptr(source) - + bsource->consumed); - ae_f16x4 *out = (ae_f16x4 *)audio_stream_wrap(sink, (char *)audio_stream_get_wptr(sink) - + bsink->size); - const int channels_count = audio_stream_get_channels(sink); + const ae_f16x4 *in = source->ptr; + ae_f16x4 *out = sink->ptr; + const int channels_count = cd->channels; const int inc = sizeof(ae_f32x2); int samples = channels_count * frames; ae_f32x2 temp; @@ -557,9 +519,9 @@ static void vol_passthrough_s16_to_s16(struct processing_module *mod, AE_SETCEND1(cd->peak_vol + channels_count * 4); while (samples) { - m = audio_stream_samples_without_wrap_s16(source, in); + m = cir_buf_samples_without_wrap_s16(in, source->buf_end); n = MIN(m, samples); - m = audio_stream_samples_without_wrap_s16(sink, out); + m = cir_buf_samples_without_wrap_s16(out, sink->buf_end); n = MIN(m, n); inu = AE_LA64_PP(in); m = n >> 2; @@ -594,10 +556,8 @@ static void vol_passthrough_s16_to_s16(struct processing_module *mod, } samples -= n; - in = (ae_f16x4 *)audio_stream_wrap(source, in); - out = (ae_f16x4 *)audio_stream_wrap(sink, out); - bsource->consumed += VOL_S16_SAMPLES_TO_BYTES(n); - bsink->size += VOL_S16_SAMPLES_TO_BYTES(n); + in = cir_buf_wrap(in, source->buf_start, source->buf_end); + out = cir_buf_wrap(out, sink->buf_start, sink->buf_end); } for (i = 0; i < channels_count; i++) { diff --git a/src/audio/volume/volume_hifi5.c b/src/audio/volume/volume_hifi5.c index 794330cddd9b..4d4041b38145 100644 --- a/src/audio/volume/volume_hifi5.c +++ b/src/audio/volume/volume_hifi5.c @@ -55,13 +55,11 @@ static void vol_store_gain(struct vol_data *cd, const int channels_count) * \param[in] frames Number of frames to process. * \param[in] attenuation factor for peakmeter adjustment (unused) */ -static void vol_s24_to_s24_s32(struct processing_module *mod, struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, +static void vol_s24_to_s24_s32(struct processing_module *mod, struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { struct vol_data *cd = module_get_private_data(mod); - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; ae_int32x2 in_sample, in_sample1; ae_int32x2 out_sample, out_sample1; ae_int32x2 volume, volume1; @@ -71,13 +69,9 @@ static void vol_s24_to_s24_s32(struct processing_module *mod, struct input_strea ae_int32x4 *vol; ae_valignx2 inu; ae_valignx2 outu = AE_ZALIGN128(); - ae_int32x4 *in = (ae_int32x4 *)audio_stream_wrap(source, - (char *)audio_stream_get_rptr(source) - + bsource->consumed); - ae_int32x4 *out = (ae_int32x4 *)audio_stream_wrap(sink, - (char *)audio_stream_get_wptr(sink) - + bsink->size); - const int channels_count = audio_stream_get_channels(sink); + const ae_int32x4 *in = source->ptr; + ae_int32x4 *out = sink->ptr; + const int channels_count = cd->channels; const int inc = sizeof(ae_int32x4); int samples = channels_count * frames; @@ -95,13 +89,10 @@ static void vol_s24_to_s24_s32(struct processing_module *mod, struct input_strea AE_SETCBEGIN0(buf); AE_SETCEND0(buf_end); - bsource->consumed += VOL_S32_SAMPLES_TO_BYTES(samples); - bsink->size += VOL_S32_SAMPLES_TO_BYTES(samples); - while (samples) { - m = audio_stream_samples_without_wrap_s24(source, in); + m = cir_buf_samples_without_wrap_s32(in, source->buf_end); n = MIN(m, samples); - m = audio_stream_samples_without_wrap_s24(sink, out); + m = cir_buf_samples_without_wrap_s32(out, sink->buf_end); n = MIN(m, n); inu = AE_LA128_PP(in); /* process four continuous samples per iteration */ @@ -138,8 +129,8 @@ static void vol_s24_to_s24_s32(struct processing_module *mod, struct input_strea } AE_SA128POS_FP(outu, out); samples -= n; - in = audio_stream_wrap(source, in); - out = audio_stream_wrap(sink, out); + in = cir_buf_wrap(in, source->buf_start, source->buf_end); + out = cir_buf_wrap(out, sink->buf_start, sink->buf_end); } } @@ -152,31 +143,23 @@ static void vol_s24_to_s24_s32(struct processing_module *mod, struct input_strea * \param[in] attenuation factor for peakmeter adjustment (unused) */ static void vol_passthrough_s24_to_s24_s32(struct processing_module *mod, - struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, + struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; + struct vol_data *cd = module_get_private_data(mod); ae_int32x2 in_sample, in_sample1; int i, n, m; ae_valignx2 inu; ae_valignx2 outu = AE_ZALIGN128(); - ae_int32x4 *in = (ae_int32x4 *)audio_stream_wrap(source, - (char *)audio_stream_get_rptr(source) - + bsource->consumed); - ae_int32x4 *out = (ae_int32x4 *)audio_stream_wrap(sink, - (char *)audio_stream_get_wptr(sink) - + bsink->size); - int samples = audio_stream_get_channels(sink) * frames; - - bsource->consumed += VOL_S32_SAMPLES_TO_BYTES(samples); - bsink->size += VOL_S32_SAMPLES_TO_BYTES(samples); + const ae_int32x4 *in = source->ptr; + ae_int32x4 *out = sink->ptr; + int samples = cd->channels * frames; while (samples) { - m = audio_stream_samples_without_wrap_s24(source, in); + m = cir_buf_samples_without_wrap_s32(in, source->buf_end); n = MIN(m, samples); - m = audio_stream_samples_without_wrap_s24(sink, out); + m = cir_buf_samples_without_wrap_s32(out, sink->buf_end); n = MIN(m, n); inu = AE_LA128_PP(in); /* process 4 continuous samples once */ @@ -188,8 +171,8 @@ static void vol_passthrough_s24_to_s24_s32(struct processing_module *mod, } AE_SA128POS_FP(outu, out); samples -= n; - in = audio_stream_wrap(source, in); - out = audio_stream_wrap(sink, out); + in = cir_buf_wrap(in, source->buf_start, source->buf_end); + out = cir_buf_wrap(out, sink->buf_start, sink->buf_end); } } @@ -204,13 +187,11 @@ static void vol_passthrough_s24_to_s24_s32(struct processing_module *mod, * \param[in] frames Number of frames to process. * \param[in] attenuation factor for peakmeter adjustment (unused) */ -static void vol_s32_to_s24_s32(struct processing_module *mod, struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, +static void vol_s32_to_s24_s32(struct processing_module *mod, struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { struct vol_data *cd = module_get_private_data(mod); - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; ae_int32x2 in_sample, in_sample1; ae_int32x2 out_sample, out_sample1; ae_int32x2 volume, volume1; @@ -220,15 +201,11 @@ static void vol_s32_to_s24_s32(struct processing_module *mod, struct input_strea ae_int32x4 *vol; ae_valignx2 inu; ae_valignx2 outu = AE_ZALIGN128(); - const int channels_count = audio_stream_get_channels(sink); + const int channels_count = cd->channels; const int inc = sizeof(ae_int32x4); int samples = channels_count * frames; - ae_int32x4 *in = (ae_int32x4 *)audio_stream_wrap(source, - (char *)audio_stream_get_rptr(source) - + bsource->consumed); - ae_int32x4 *out = (ae_int32x4 *)audio_stream_wrap(sink, - (char *)audio_stream_get_wptr(sink) - + bsink->size); + const ae_int32x4 *in = source->ptr; + ae_int32x4 *out = sink->ptr; /** to ensure the address is 16-byte aligned and avoid risk of * error loading of volume gain while the cd->vol would be set @@ -244,13 +221,10 @@ static void vol_s32_to_s24_s32(struct processing_module *mod, struct input_strea AE_SETCBEGIN0(buf); AE_SETCEND0(buf_end); - bsource->consumed += VOL_S32_SAMPLES_TO_BYTES(samples); - bsink->size += VOL_S32_SAMPLES_TO_BYTES(samples); - while (samples) { - m = audio_stream_samples_without_wrap_s32(source, in); + m = cir_buf_samples_without_wrap_s32(in, source->buf_end); n = MIN(m, samples); - m = audio_stream_samples_without_wrap_s32(sink, out); + m = cir_buf_samples_without_wrap_s32(out, sink->buf_end); n = MIN(m, n); inu = AE_LA128_PP(in); /* process four continuous samples per iteration */ @@ -285,8 +259,8 @@ static void vol_s32_to_s24_s32(struct processing_module *mod, struct input_strea } AE_SA128POS_FP(outu, out); samples -= n; - in = audio_stream_wrap(source, in); - out = audio_stream_wrap(sink, out); + in = cir_buf_wrap(in, source->buf_start, source->buf_end); + out = cir_buf_wrap(out, sink->buf_start, sink->buf_end); } } @@ -299,31 +273,24 @@ static void vol_s32_to_s24_s32(struct processing_module *mod, struct input_strea * \param[in] attenuation factor for peakmeter adjustment (unused) */ static void vol_passthrough_s32_to_s24_s32(struct processing_module *mod, - struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, + struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; + struct vol_data *cd = module_get_private_data(mod); ae_int32x2 in_sample, in_sample1; int i, n, m; ae_valignx2 inu; ae_valignx2 outu = AE_ZALIGN128(); - const int channels_count = audio_stream_get_channels(sink); + const int channels_count = cd->channels; int samples = channels_count * frames; - ae_int32x4 *in = (ae_int32x4 *)audio_stream_wrap(source, - (char *)audio_stream_get_rptr(source) - + bsource->consumed); - ae_int32x4 *out = (ae_int32x4 *)audio_stream_wrap(sink, - (char *)audio_stream_get_wptr(sink) - + bsink->size); - - bsource->consumed += VOL_S32_SAMPLES_TO_BYTES(samples); - bsink->size += VOL_S32_SAMPLES_TO_BYTES(samples); + const ae_int32x4 *in = source->ptr; + ae_int32x4 *out = sink->ptr; + while (samples) { - m = audio_stream_samples_without_wrap_s32(source, in); + m = cir_buf_samples_without_wrap_s32(in, source->buf_end); n = MIN(m, samples); - m = audio_stream_samples_without_wrap_s32(sink, out); + m = cir_buf_samples_without_wrap_s32(out, sink->buf_end); n = MIN(m, n); inu = AE_LA128_PP(in); /* process four continuous samples per iteration */ @@ -334,8 +301,8 @@ static void vol_passthrough_s32_to_s24_s32(struct processing_module *mod, } AE_SA128POS_FP(outu, out); samples -= n; - in = audio_stream_wrap(source, in); - out = audio_stream_wrap(sink, out); + in = cir_buf_wrap(in, source->buf_start, source->buf_end); + out = cir_buf_wrap(out, sink->buf_start, sink->buf_end); } } #endif /* CONFIG_FORMAT_S32LE */ @@ -349,13 +316,11 @@ static void vol_passthrough_s32_to_s24_s32(struct processing_module *mod, * \param[in] frames Number of frames to process. * \param[in] attenuation factor for peakmeter adjustment (unused) */ -static void vol_s16_to_s16(struct processing_module *mod, struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, +static void vol_s16_to_s16(struct processing_module *mod, struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { struct vol_data *cd = module_get_private_data(mod); - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; ae_int32x2 volume, volume1, volume2, volume3; ae_int32x2 out_temp, out_temp1; ae_int16x4 in_sample, in_sample1; @@ -366,13 +331,9 @@ static void vol_s16_to_s16(struct processing_module *mod, struct input_stream_bu ae_int32x4 *vol; ae_valignx2 inu; ae_valignx2 outu = AE_ZALIGN128(); - ae_int16x8 *in = (ae_int16x8 *)audio_stream_wrap(source, - (char *)audio_stream_get_rptr(source) - + bsource->consumed); - ae_int16x8 *out = (ae_int16x8 *)audio_stream_wrap(sink, - (char *)audio_stream_get_wptr(sink) - + bsink->size); - const int channels_count = audio_stream_get_channels(sink); + const ae_int16x8 *in = source->ptr; + ae_int16x8 *out = sink->ptr; + const int channels_count = cd->channels; const int inc = sizeof(ae_int32x4); int samples = channels_count * frames; @@ -392,9 +353,9 @@ static void vol_s16_to_s16(struct processing_module *mod, struct input_stream_bu AE_SETCEND0(buf_end); while (samples) { - m = audio_stream_samples_without_wrap_s16(source, in); + m = cir_buf_samples_without_wrap_s16(in, source->buf_end); n = MIN(m, samples); - m = audio_stream_samples_without_wrap_s16(sink, out); + m = cir_buf_samples_without_wrap_s16(out, sink->buf_end); n = MIN(m, n); inu = AE_LA128_PP(in); for (i = 0; i < n; i += 8) { @@ -440,10 +401,8 @@ static void vol_s16_to_s16(struct processing_module *mod, struct input_stream_bu } AE_SA128POS_FP(outu, out); samples -= n; - bsource->consumed += VOL_S16_SAMPLES_TO_BYTES(n); - bsink->size += VOL_S16_SAMPLES_TO_BYTES(n); - in = audio_stream_wrap(source, in); - out = audio_stream_wrap(sink, out); + in = cir_buf_wrap(in, source->buf_start, source->buf_end); + out = cir_buf_wrap(out, sink->buf_start, sink->buf_end); } } @@ -456,31 +415,24 @@ static void vol_s16_to_s16(struct processing_module *mod, struct input_stream_bu * \param[in] attenuation factor for peakmeter adjustment (unused) */ static void vol_passthrough_s16_to_s16(struct processing_module *mod, - struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, + struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; + struct vol_data *cd = module_get_private_data(mod); ae_int16x4 in_sample, in_sample1; int i, n, m; ae_valignx2 inu; ae_valignx2 outu = AE_ZALIGN128(); - ae_int16x8 *in = (ae_int16x8 *)audio_stream_wrap(source, - (char *)audio_stream_get_rptr(source) - + bsource->consumed); - ae_int16x8 *out = (ae_int16x8 *)audio_stream_wrap(sink, - (char *)audio_stream_get_wptr(sink) - + bsink->size); - const int channels_count = audio_stream_get_channels(sink); + const ae_int16x8 *in = source->ptr; + ae_int16x8 *out = sink->ptr; + const int channels_count = cd->channels; int samples = channels_count * frames; - bsource->consumed += VOL_S16_SAMPLES_TO_BYTES(samples); - bsink->size += VOL_S16_SAMPLES_TO_BYTES(samples); while (samples) { - m = audio_stream_samples_without_wrap_s16(source, in); + m = cir_buf_samples_without_wrap_s16(in, source->buf_end); n = MIN(m, samples); - m = audio_stream_samples_without_wrap_s16(sink, out); + m = cir_buf_samples_without_wrap_s16(out, sink->buf_end); n = MIN(m, n); inu = AE_LA128_PP(in); for (i = 0; i < n; i += 8) { @@ -490,8 +442,8 @@ static void vol_passthrough_s16_to_s16(struct processing_module *mod, } AE_SA128POS_FP(outu, out); samples -= n; - in = audio_stream_wrap(source, in); - out = audio_stream_wrap(sink, out); + in = cir_buf_wrap(in, source->buf_start, source->buf_end); + out = cir_buf_wrap(out, sink->buf_start, sink->buf_end); } } #endif /* CONFIG_FORMAT_S16LE */ diff --git a/src/audio/volume/volume_hifi5_with_peakvol.c b/src/audio/volume/volume_hifi5_with_peakvol.c index 16d2f7cb8bf9..5d085dfe6522 100644 --- a/src/audio/volume/volume_hifi5_with_peakvol.c +++ b/src/audio/volume/volume_hifi5_with_peakvol.c @@ -55,13 +55,10 @@ static inline void vol_store_gain(struct vol_data *cd, const int channels_count) * \param[in] frames Number of frames to process. * \param[in] attenuation factor for peakmeter adjustment */ -static void vol_s24_to_s24_s32(struct processing_module *mod, struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, - uint32_t attenuation) +static void vol_s24_to_s24_s32(struct processing_module *mod, struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { struct vol_data *cd = module_get_private_data(mod); - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; ae_int32x2 in_sample, in_sample1; ae_int32x2 out_sample, out_sample1; ae_int32x2 volume, volume1; @@ -69,13 +66,9 @@ static void vol_s24_to_s24_s32(struct processing_module *mod, struct input_strea ae_int32x4 *vol; ae_valignx2 inu; ae_valignx2 outu = AE_ZALIGN128(); - ae_int32x4 *in = (ae_int32x4 *)audio_stream_wrap(source, - (char *)audio_stream_get_rptr(source) - + bsource->consumed); - ae_int32x4 *out = (ae_int32x4 *)audio_stream_wrap(sink, - (char *)audio_stream_get_wptr(sink) - + bsink->size); - const int channels_count = audio_stream_get_channels(sink); + const ae_int32x4 *in = source->ptr; + ae_int32x4 *out = sink->ptr; + const int channels_count = cd->channels; const int inc = sizeof(ae_int32x4); int samples = channels_count * frames; ae_int32x2 temp, temp1; @@ -97,13 +90,10 @@ static void vol_s24_to_s24_s32(struct processing_module *mod, struct input_strea AE_SETCBEGIN0(vol); AE_SETCEND0(cd->vol + channels_count * 4); - bsource->consumed += VOL_S32_SAMPLES_TO_BYTES(samples); - bsink->size += VOL_S32_SAMPLES_TO_BYTES(samples); - while (samples) { - m = audio_stream_samples_without_wrap_s32(source, in); + m = cir_buf_samples_without_wrap_s32(in, source->buf_end); n = MIN(m, samples); - m = audio_stream_samples_without_wrap_s16(sink, out); + m = cir_buf_samples_without_wrap_s16(out, sink->buf_end); n = MIN(m, n); inu = AE_LA128_PP(in); /* process four continuous samples per loop */ @@ -145,8 +135,8 @@ static void vol_s24_to_s24_s32(struct processing_module *mod, struct input_strea } AE_SA128POS_FP(outu, out); samples -= n; - in = audio_stream_wrap(source, in); - out = audio_stream_wrap(sink, out); + in = cir_buf_wrap(in, source->buf_start, source->buf_end); + out = cir_buf_wrap(out, sink->buf_start, sink->buf_end); } for (i = 0; i < channels_count; i++) { @@ -166,24 +156,18 @@ static void vol_s24_to_s24_s32(struct processing_module *mod, struct input_strea * \param[in] attenuation factor for peakmeter adjustment */ static void vol_passthrough_s24_to_s24_s32(struct processing_module *mod, - struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, + struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { struct vol_data *cd = module_get_private_data(mod); - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; ae_int32x2 in_sample, in_sample1; int i, n, m; ae_valignx2 inu; ae_valignx2 outu = AE_ZALIGN128(); - ae_int32x4 *in = (ae_int32x4 *)audio_stream_wrap(source, - (char *)audio_stream_get_rptr(source) - + bsource->consumed); - ae_int32x4 *out = (ae_int32x4 *)audio_stream_wrap(sink, - (char *)audio_stream_get_wptr(sink) - + bsink->size); - const int channels_count = audio_stream_get_channels(sink); + const ae_int32x4 *in = source->ptr; + ae_int32x4 *out = sink->ptr; + const int channels_count = cd->channels; const int inc = sizeof(ae_int32x4); int samples = channels_count * frames; ae_int32x2 temp, temp1; @@ -193,13 +177,10 @@ static void vol_passthrough_s24_to_s24_s32(struct processing_module *mod, AE_SETCBEGIN1(cd->peak_vol); AE_SETCEND1(cd->peak_vol + channels_count * 4); - bsource->consumed += VOL_S32_SAMPLES_TO_BYTES(samples); - bsink->size += VOL_S32_SAMPLES_TO_BYTES(samples); - while (samples) { - m = audio_stream_samples_without_wrap_s32(source, in); + m = cir_buf_samples_without_wrap_s32(in, source->buf_end); n = MIN(m, samples); - m = audio_stream_samples_without_wrap_s16(sink, out); + m = cir_buf_samples_without_wrap_s16(out, sink->buf_end); n = MIN(m, n); inu = AE_LA128_PP(in); /* process four continuous samples per loop */ @@ -216,8 +197,8 @@ static void vol_passthrough_s24_to_s24_s32(struct processing_module *mod, } AE_SA128POS_FP(outu, out); samples -= n; - in = audio_stream_wrap(source, in); - out = audio_stream_wrap(sink, out); + in = cir_buf_wrap(in, source->buf_start, source->buf_end); + out = cir_buf_wrap(out, sink->buf_start, sink->buf_end); } for (i = 0; i < channels_count; i++) { m = MAX(cd->peak_vol[i], cd->peak_vol[i + channels_count]); @@ -237,13 +218,10 @@ static void vol_passthrough_s24_to_s24_s32(struct processing_module *mod, * \param[in] frames Number of frames to process. * \param[in] attenuation factor for peakmeter adjustment */ -static void vol_s32_to_s24_s32(struct processing_module *mod, struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, - uint32_t attenuation) +static void vol_s32_to_s24_s32(struct processing_module *mod, struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { struct vol_data *cd = module_get_private_data(mod); - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; ae_int32x2 in_sample, in_sample1; ae_int32x2 out_sample, out_sample1; ae_int32x2 volume, volume1; @@ -253,15 +231,11 @@ static void vol_s32_to_s24_s32(struct processing_module *mod, struct input_strea ae_int32x4 *vol; ae_valignx2 inu; ae_valignx2 outu = AE_ZALIGN128(); - const int channels_count = audio_stream_get_channels(sink); + const int channels_count = cd->channels; const int inc = sizeof(ae_int32x4); int samples = channels_count * frames; - ae_int32x4 *in = (ae_int32x4 *)audio_stream_wrap(source, - (char *)audio_stream_get_rptr(source) - + bsource->consumed); - ae_int32x4 *out = (ae_int32x4 *)audio_stream_wrap(sink, - (char *)audio_stream_get_wptr(sink) - + bsink->size); + const ae_int32x4 *in = source->ptr; + ae_int32x4 *out = sink->ptr; ae_int32x2 temp, temp1; ae_int32x4 *peakvol = (ae_int32x4 *)cd->peak_vol; @@ -283,13 +257,10 @@ static void vol_s32_to_s24_s32(struct processing_module *mod, struct input_strea AE_SETCBEGIN0(buf); AE_SETCEND0(buf_end); - bsource->consumed += VOL_S32_SAMPLES_TO_BYTES(samples); - bsink->size += VOL_S32_SAMPLES_TO_BYTES(samples); - while (samples) { - m = audio_stream_samples_without_wrap_s32(source, in); + m = cir_buf_samples_without_wrap_s32(in, source->buf_end); n = MIN(m, samples); - m = audio_stream_samples_without_wrap_s32(sink, out); + m = cir_buf_samples_without_wrap_s32(out, sink->buf_end); n = MIN(m, n); inu = AE_LA128_PP(in); /* process four continuous samples per loop */ @@ -329,8 +300,8 @@ static void vol_s32_to_s24_s32(struct processing_module *mod, struct input_strea } AE_SA128POS_FP(outu, out); samples -= n; - in = audio_stream_wrap(source, in); - out = audio_stream_wrap(sink, out); + in = cir_buf_wrap(in, source->buf_start, source->buf_end); + out = cir_buf_wrap(out, sink->buf_start, sink->buf_end); } for (i = 0; i < channels_count; i++) { m = MAX(cd->peak_vol[i], cd->peak_vol[i + channels_count]); @@ -349,39 +320,31 @@ static void vol_s32_to_s24_s32(struct processing_module *mod, struct input_strea * \param[in] attenuation factor for peakmeter adjustment */ static void vol_passthrough_s32_to_s24_s32(struct processing_module *mod, - struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, + struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { struct vol_data *cd = module_get_private_data(mod); - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; ae_int32x2 in_sample, in_sample1; int i, n, m; ae_valignx2 inu; ae_valignx2 outu = AE_ZALIGN128(); - const int channels_count = audio_stream_get_channels(sink); + const int channels_count = cd->channels; const int inc = sizeof(ae_int32x4); int samples = channels_count * frames; - ae_int32x4 *in = (ae_int32x4 *)audio_stream_wrap(source, - (char *)audio_stream_get_rptr(source) - + bsource->consumed); - ae_int32x4 *out = (ae_int32x4 *)audio_stream_wrap(sink, - (char *)audio_stream_get_wptr(sink) - + bsink->size); + const ae_int32x4 *in = source->ptr; + ae_int32x4 *out = sink->ptr; ae_int32x2 temp, temp1; ae_int32x4 *peakvol = (ae_int32x4 *)cd->peak_vol; /* Set peakvol(which stores the peak volume data four times) as circular buffer */ AE_SETCBEGIN1(cd->peak_vol); AE_SETCEND1(cd->peak_vol + channels_count * 4); - bsource->consumed += VOL_S32_SAMPLES_TO_BYTES(samples); - bsink->size += VOL_S32_SAMPLES_TO_BYTES(samples); while (samples) { - m = audio_stream_samples_without_wrap_s32(source, in); + m = cir_buf_samples_without_wrap_s32(in, source->buf_end); n = MIN(m, samples); - m = audio_stream_samples_without_wrap_s32(sink, out); + m = cir_buf_samples_without_wrap_s32(out, sink->buf_end); n = MIN(m, n); inu = AE_LA128_PP(in); /* process four continuous samples per iteration */ @@ -398,8 +361,8 @@ static void vol_passthrough_s32_to_s24_s32(struct processing_module *mod, } AE_SA128POS_FP(outu, out); samples -= n; - in = audio_stream_wrap(source, in); - out = audio_stream_wrap(sink, out); + in = cir_buf_wrap(in, source->buf_start, source->buf_end); + out = cir_buf_wrap(out, sink->buf_start, sink->buf_end); } for (i = 0; i < channels_count; i++) { m = MAX(cd->peak_vol[i], cd->peak_vol[i + channels_count]); @@ -419,13 +382,10 @@ static void vol_passthrough_s32_to_s24_s32(struct processing_module *mod, * \param[in] frames Number of frames to process. * \param[in] attenuation factor for peakmeter adjustment (unused for 16bit) */ -static void vol_s16_to_s16(struct processing_module *mod, struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, - uint32_t attenuation) +static void vol_s16_to_s16(struct processing_module *mod, struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { struct vol_data *cd = module_get_private_data(mod); - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; ae_int32x2 volume, volume1, volume2, volume3; ae_int32x2 out_temp, out_temp1; ae_int16x4 in_sample, in_sample1; @@ -436,13 +396,9 @@ static void vol_s16_to_s16(struct processing_module *mod, struct input_stream_bu ae_int32x4 *vol; ae_valignx2 inu; ae_valignx2 outu = AE_ZALIGN128(); - ae_int16x8 *in = (ae_int16x8 *)audio_stream_wrap(source, - (char *)audio_stream_get_rptr(source) - + bsource->consumed); - ae_int16x8 *out = (ae_int16x8 *)audio_stream_wrap(sink, - (char *)audio_stream_get_wptr(sink) - + bsink->size); - const int channels_count = audio_stream_get_channels(sink); + const ae_int16x8 *in = source->ptr; + ae_int16x8 *out = sink->ptr; + const int channels_count = cd->channels; const int inc = sizeof(ae_int32x4); int samples = channels_count * frames; ae_int32x2 temp, temp1; @@ -468,9 +424,9 @@ static void vol_s16_to_s16(struct processing_module *mod, struct input_stream_bu AE_SETCEND0(buf_end); while (samples) { - m = audio_stream_samples_without_wrap_s16(source, in); + m = cir_buf_samples_without_wrap_s16(in, source->buf_end); n = MIN(m, samples); - m = audio_stream_samples_without_wrap_s16(sink, out); + m = cir_buf_samples_without_wrap_s16(out, sink->buf_end); n = MIN(m, n); inu = AE_LA128_PP(in); for (i = 0; i < n; i += 8) { @@ -524,10 +480,8 @@ static void vol_s16_to_s16(struct processing_module *mod, struct input_stream_bu } AE_SA128POS_FP(outu, out); samples -= n; - in = audio_stream_wrap(source, in); - out = audio_stream_wrap(sink, out); - bsource->consumed += VOL_S16_SAMPLES_TO_BYTES(n); - bsink->size += VOL_S16_SAMPLES_TO_BYTES(n); + in = cir_buf_wrap(in, source->buf_start, source->buf_end); + out = cir_buf_wrap(out, sink->buf_start, sink->buf_end); } for (i = 0; i < channels_count; i++) { m = MAX(cd->peak_vol[i], cd->peak_vol[i + channels_count]); @@ -546,24 +500,18 @@ static void vol_s16_to_s16(struct processing_module *mod, struct input_stream_bu * \param[in] attenuation factor for peakmeter adjustment (unused for 16bit) */ static void vol_passthrough_s16_to_s16(struct processing_module *mod, - struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, uint32_t frames, + struct cir_buf_source *source, + struct cir_buf_sink *sink, uint32_t frames, uint32_t attenuation) { struct vol_data *cd = module_get_private_data(mod); - struct audio_stream *source = bsource->data; - struct audio_stream *sink = bsink->data; ae_int16x4 in_sample, in_sample1; int i, n, m; ae_valignx2 inu; ae_valignx2 outu = AE_ZALIGN128(); - ae_int16x8 *in = (ae_int16x8 *)audio_stream_wrap(source, - (char *)audio_stream_get_rptr(source) - + bsource->consumed); - ae_int16x8 *out = (ae_int16x8 *)audio_stream_wrap(sink, - (char *)audio_stream_get_wptr(sink) - + bsink->size); - const int channels_count = audio_stream_get_channels(sink); + const ae_int16x8 *in = source->ptr; + ae_int16x8 *out = sink->ptr; + const int channels_count = cd->channels; const int inc = sizeof(ae_int32x4); int samples = channels_count * frames; ae_int32x2 temp, temp1; @@ -574,9 +522,9 @@ static void vol_passthrough_s16_to_s16(struct processing_module *mod, AE_SETCEND1(cd->peak_vol + channels_count * 4); while (samples) { - m = audio_stream_samples_without_wrap_s16(source, in); + m = cir_buf_samples_without_wrap_s16(in, source->buf_end); n = MIN(m, samples); - m = audio_stream_samples_without_wrap_s16(sink, out); + m = cir_buf_samples_without_wrap_s16(out, sink->buf_end); n = MIN(m, n); inu = AE_LA128_PP(in); for (i = 0; i < n; i += 8) { @@ -598,10 +546,8 @@ static void vol_passthrough_s16_to_s16(struct processing_module *mod, } AE_SA128POS_FP(outu, out); samples -= n; - in = audio_stream_wrap(source, in); - out = audio_stream_wrap(sink, out); - bsource->consumed += VOL_S16_SAMPLES_TO_BYTES(n); - bsink->size += VOL_S16_SAMPLES_TO_BYTES(n); + in = cir_buf_wrap(in, source->buf_start, source->buf_end); + out = cir_buf_wrap(out, sink->buf_start, sink->buf_end); } for (i = 0; i < channels_count; i++) { m = MAX(cd->peak_vol[i], cd->peak_vol[i + channels_count]); diff --git a/src/audio/volume/volume_ipc3.c b/src/audio/volume/volume_ipc3.c index 6a88b68e6ae8..053b41e32152 100644 --- a/src/audio/volume/volume_ipc3.c +++ b/src/audio/volume/volume_ipc3.c @@ -157,8 +157,8 @@ int volume_init(struct processing_module *mod) break; default: comp_err(dev, "invalid ramp type %d", vol->ramp); - mod_free(mod, cd); mod_free(mod, cd->vol); + mod_free(mod, cd); return -EINVAL; } diff --git a/src/audio/volume/volume_ipc4.c b/src/audio/volume/volume_ipc4.c index 57a5f427903e..e111752d2b78 100644 --- a/src/audio/volume/volume_ipc4.c +++ b/src/audio/volume/volume_ipc4.c @@ -115,6 +115,7 @@ int volume_init(struct processing_module *mod) uint32_t channels_count; uint8_t channel_cfg; uint8_t channel; + bool all_channels; uint32_t instance_id = IPC4_INST_ID(dev_comp_id(dev)); if (instance_id >= IPC4_MAX_PEAK_VOL_REG_SLOTS) { @@ -127,6 +128,26 @@ int volume_init(struct processing_module *mod) return -EINVAL; } + /* The payload must hold at least one config entry, which is read below + * to detect the all-channels form. + */ + if (cfg->size < sizeof(*vol) + sizeof(vol->config[0])) { + comp_err(dev, "Invalid init payload size %zu", cfg->size); + return -EINVAL; + } + + /* In the all-channels form a single entry applies to every channel; + * otherwise the payload must hold one entry per channel as they are + * each read below. + */ + all_channels = vol->config[0].channel_id == IPC4_ALL_CHANNELS_MASK; + if (!all_channels && + cfg->size < sizeof(*vol) + channels_count * sizeof(vol->config[0])) { + comp_err(dev, "Invalid init payload size %zu for %u channels", + cfg->size, channels_count); + return -EINVAL; + } + cd = mod_zalloc(mod, sizeof(struct vol_data)); if (!cd) return -ENOMEM; @@ -156,16 +177,13 @@ int volume_init(struct processing_module *mod) md->private = cd; for (channel = 0; channel < channels_count; channel++) { - if (vol->config[0].channel_id == IPC4_ALL_CHANNELS_MASK) - channel_cfg = 0; - else - channel_cfg = channel; + channel_cfg = all_channels ? 0 : channel; target_volume[channel] = - convert_volume_ipc4_to_ipc3(dev, vol->config[channel].target_volume); + convert_volume_ipc4_to_ipc3(dev, vol->config[channel_cfg].target_volume); set_volume_ipc4(cd, channel, - target_volume[channel_cfg], + target_volume[channel], vol->config[channel_cfg].curve_type, vol->config[channel_cfg].curve_duration); @@ -268,8 +286,10 @@ static int volume_set_attenuation(struct processing_module *mod, const uint8_t * struct comp_dev *dev = mod->dev; uint32_t attenuation; - /* only support attenuation in format of 32bit */ - if (data_size > sizeof(uint32_t)) { + /* only support attenuation in format of 32bit; the payload is + * dereferenced as a uint32_t below so it must be exactly that size + */ + if (data_size != (int)sizeof(uint32_t)) { comp_err(dev, "attenuation data size %d is incorrect", data_size); return -EINVAL; } diff --git a/src/debug/debug_stream/debug_stream_slot.c b/src/debug/debug_stream/debug_stream_slot.c index 82816a6788f3..db27490a98a4 100644 --- a/src/debug/debug_stream/debug_stream_slot.c +++ b/src/debug/debug_stream/debug_stream_slot.c @@ -12,6 +12,10 @@ #include #include +#ifdef CONFIG_USERSPACE +#include +#endif + LOG_MODULE_REGISTER(debug_stream_slot); struct cpu_mutex { @@ -66,7 +70,7 @@ debug_stream_get_circular_buffer(struct debug_stream_section_descriptor *desc, u return (struct debug_stream_circular_buf *) (((uint8_t *)hdr) + desc->offset); } -int debug_stream_slot_send_record(struct debug_stream_record *rec) +int z_impl_debug_stream_slot_send_record(struct debug_stream_record *rec) { struct debug_stream_section_descriptor desc = { 0 }; struct debug_stream_circular_buf *buf = @@ -119,6 +123,16 @@ int debug_stream_slot_send_record(struct debug_stream_record *rec) return 0; } +#ifdef CONFIG_USERSPACE +static inline int z_vrfy_debug_stream_slot_send_record(struct debug_stream_record *rec) +{ + K_OOPS(K_SYSCALL_MEMORY_READ(rec, sizeof(*rec))); + K_OOPS(K_SYSCALL_MEMORY_READ(rec, rec->size_words * sizeof(uint32_t))); + return z_impl_debug_stream_slot_send_record(rec); +} +#include +#endif + static int debug_stream_slot_init(void) { struct debug_stream_slot_hdr *hdr = debug_stream_get_slot(); diff --git a/src/debug/debug_stream/debug_stream_text_msg.c b/src/debug/debug_stream/debug_stream_text_msg.c index 97db0fd29330..f4b67d4d307a 100644 --- a/src/debug/debug_stream/debug_stream_text_msg.c +++ b/src/debug/debug_stream/debug_stream_text_msg.c @@ -43,6 +43,7 @@ void ds_msg(const char *format, ...) ds_vamsg(format, args); va_end(args); } +EXPORT_SYMBOL(ds_msg); #if defined(CONFIG_EXCEPTION_DUMP_HOOK) /* The debug stream debug window slot is 4k, and when it is split diff --git a/src/debug/telemetry/performance_monitor.c b/src/debug/telemetry/performance_monitor.c index b7aedc5ffa7c..f84b122b4d8a 100644 --- a/src/debug/telemetry/performance_monitor.c +++ b/src/debug/telemetry/performance_monitor.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -427,11 +428,13 @@ SYS_BITARRAY_DEFINE_STATIC(io_performance_data_bit_array, PERFORMANCE_DATA_ENTRI static struct io_perf_monitor_ctx perf_monitor_ctx; static struct io_perf_data_item io_perf_data_items[IO_PERFORMANCE_MAX_ENTRIES]; -int io_perf_monitor_init(void) +__cold int io_perf_monitor_init(void) { int ret; struct io_perf_monitor_ctx *self = &perf_monitor_ctx; + assert_can_be_cold(); + k_spinlock_init(&self->lock); k_spinlock_key_t key = k_spin_lock(&self->lock); diff --git a/src/drivers/amd/common/acp_dma.c b/src/drivers/amd/common/acp_dma.c index bd1e6cba16a2..df68b8bebfb6 100644 --- a/src/drivers/amd/common/acp_dma.c +++ b/src/drivers/amd/common/acp_dma.c @@ -314,6 +314,7 @@ static int acp_dma_probe(struct dma *dma) sizeof(struct acp_dma_chan_data)); if (!acp_dma_chan) { rfree(dma->chan); + dma->chan = NULL; tr_err(&acpdma_tr, "acp-dma: %d channel %d private data alloc failed", dma->plat_data.id, channel); return -ENOMEM; diff --git a/src/drivers/dw/dma.c b/src/drivers/dw/dma.c index 102cbbe0d7ba..005980454798 100644 --- a/src/drivers/dw/dma.c +++ b/src/drivers/dw/dma.c @@ -532,8 +532,6 @@ static int dw_dma_set_config(struct dma_chan_data *channel, /* do we need to realloc descriptors */ if (config->elem_array.count != channel->desc_count) { - channel->desc_count = config->elem_array.count; - /* * Allocate descriptors for channel. They must be cache-line * size aligned to avoid corrupting adjacent memory when @@ -542,18 +540,25 @@ static int dw_dma_set_config(struct dma_chan_data *channel, * allocations on Zephyr to always force cache-line size * alignment. */ - if (dw_chan->lli) - rfree(dw_chan->lli); + rfree(dw_chan->lli); dw_chan->lli = rmalloc(SOF_MEM_FLAG_KERNEL | SOF_MEM_FLAG_COHERENT | SOF_MEM_FLAG_DMA, - sizeof(struct dw_lli) * channel->desc_count); + sizeof(struct dw_lli) * config->elem_array.count); if (!dw_chan->lli) { tr_err(&dwdma_tr, "dma %d channel %d lli alloc failed", channel->dma->plat_data.id, channel->index); + /* allocation failed, so dw_chan->lli is now NULL; reset + * the count to match it so a later config does not + * bzero() a NULL pointer using a stale count + */ + channel->desc_count = 0; ret = -ENOMEM; goto out; } + + /* only commit the new count once the buffer is allocated */ + channel->desc_count = config->elem_array.count; } /* initialise descriptors */ diff --git a/src/drivers/imx/sdma.c b/src/drivers/imx/sdma.c index a613f98b40c5..e14db9e6796e 100644 --- a/src/drivers/imx/sdma.c +++ b/src/drivers/imx/sdma.c @@ -443,7 +443,7 @@ static void sdma_enable_event(struct dma_chan_data *channel, int eventnum) tr_dbg(&sdma_tr, "channel %d, event %d", channel->index, eventnum); - if (eventnum < 0 || eventnum > SDMA_HWEVENTS_COUNT) + if (eventnum < 0 || eventnum >= SDMA_HWEVENTS_COUNT) return; /* No change if request is invalid */ dma_reg_update_bits(channel->dma, SDMA_CHNENBL(eventnum), @@ -461,7 +461,7 @@ static void sdma_disable_event(struct dma_chan_data *channel, int eventnum) { tr_dbg(&sdma_tr, "channel %d, event %d", channel->index, eventnum); - if (eventnum < 0 || eventnum > SDMA_HWEVENTS_COUNT) + if (eventnum < 0 || eventnum >= SDMA_HWEVENTS_COUNT) return; /* No change if request is invalid */ dma_reg_update_bits(channel->dma, SDMA_CHNENBL(eventnum), diff --git a/src/drivers/interrupt.c b/src/drivers/interrupt.c index 5bc1a26e53f7..afa07c4d1d46 100644 --- a/src/drivers/interrupt.c +++ b/src/drivers/interrupt.c @@ -168,8 +168,7 @@ struct irq_cascade_desc *interrupt_get_parent(uint32_t irq) void interrupt_init(struct sof *sof) { - sof->cascade_root = platform_shared_get(&cascade_root, - sizeof(cascade_root)); + sof->cascade_root = &cascade_root; sof->cascade_root->last_irq = PLATFORM_IRQ_FIRST_CHILD - 1; k_spinlock_init(&sof->cascade_root->lock); diff --git a/src/idc/idc.c b/src/idc/idc.c index bafa44299073..145a22abfa1c 100644 --- a/src/idc/idc.c +++ b/src/idc/idc.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -199,6 +200,7 @@ static int idc_prepare(uint32_t comp_id) dev->ipc_config.core, 0); if (ret < 0) { sof_heap_free(dev->drv->user_heap, dev->task); + dev->task = NULL; goto out; } } @@ -419,14 +421,16 @@ void idc_cmd(struct idc_msg *msg) } /* Runs on each CPU */ -int idc_init(void) +__cold int idc_init(void) { struct idc **idc = idc_get(); + assert_can_be_cold(); + tr_dbg(&idc_tr, "entry"); /* initialize idc data */ - (*idc)->payload = platform_shared_get(static_payload, sizeof(static_payload)); + (*idc)->payload = static_payload; #ifdef CONFIG_SOF_TELEMETRY_IO_PERFORMANCE_MEASUREMENTS struct io_perf_data_item init_data = {IO_PERF_IDC_ID, diff --git a/src/idc/zephyr_idc.c b/src/idc/zephyr_idc.c index 8ffc770eb6ee..771ae967bfb1 100644 --- a/src/idc/zephyr_idc.c +++ b/src/idc/zephyr_idc.c @@ -135,6 +135,11 @@ int idc_send_msg(struct idc_msg *msg, uint32_t mode) int ret; int idc_send_memcpy_err __unused; + if (!cpu_is_core_enabled(target_cpu)) { + tr_err(&zephyr_idc_tr, "Core %u is down, cannot send IDC message", target_cpu); + return -EACCES; + } + k_mutex_lock(&idc_mutex, K_FOREVER); if (unlikely(work->thread)) { @@ -153,10 +158,6 @@ int idc_send_msg(struct idc_msg *msg, uint32_t mode) work->handler = idc_handler; work->sync = mode == IDC_BLOCKING; - if (!cpu_is_core_enabled(target_cpu)) { - tr_err(&zephyr_idc_tr, "Core %u is down, cannot sent IDC message", target_cpu); - return -EACCES; - } if (msg->payload) { idc_send_memcpy_err = memcpy_s(payload->data, sizeof(payload->data), msg->payload, msg->size); @@ -196,11 +197,13 @@ int idc_send_msg(struct idc_msg *msg, uint32_t mode) return ret; } -void idc_init_thread(void) +__cold void idc_init_thread(void) { char thread_name[] = "idc_p4wq0"; int cpu = cpu_get_id(); + assert_can_be_cold(); + k_p4wq_enable_static_thread(q_zephyr_idc + cpu, _p4threads_q_zephyr_idc + cpu, BIT(cpu)); diff --git a/src/include/ipc/dai-amd.h b/src/include/ipc/dai-amd.h index ceeb870c163e..4bfa10b150e8 100644 --- a/src/include/ipc/dai-amd.h +++ b/src/include/ipc/dai-amd.h @@ -23,6 +23,8 @@ struct sof_ipc_dai_acp_params { uint32_t reserved0; uint32_t fsync_rate; uint32_t tdm_slots; + uint32_t tdm_mode; + uint32_t format; } __attribute__((packed, aligned(4))); /* ACP Configuration Request - SOF_IPC_DAI_AMD_SDW_CONFIG */ diff --git a/src/include/ipc/dai.h b/src/include/ipc/dai.h index dfc2a3e9ffb0..49ffa5d5fa94 100644 --- a/src/include/ipc/dai.h +++ b/src/include/ipc/dai.h @@ -97,6 +97,7 @@ enum sof_ipc_dai_type { SOF_DAI_IMX_MICFIL, /**< i.MX MICFIL */ SOF_DAI_AMD_SDW, /**< Amd SDW */ SOF_DAI_INTEL_UAOL, /**< Intel UAOL */ + SOF_DAI_AMD_TDM /**< Amd TDM */ }; #define SOF_DAI_CONFIG_HW_SPEC_OFFSET offsetof(struct sof_ipc_dai_config, ssp) @@ -126,7 +127,7 @@ struct sof_ipc_dai_config { struct sof_ipc_dai_acp_params acpbt; struct sof_ipc_dai_acp_params acpsp; struct sof_ipc_dai_acpdmic_params acpdmic; - struct sof_ipc_dai_acp_params acphs; + struct sof_ipc_dai_acp_params acptdm; struct sof_ipc_dai_afe_params afe; struct sof_ipc_dai_micfil_params micfil; struct sof_ipc_dai_acp_sdw_params acpsdw; diff --git a/src/include/ipc4/module.h b/src/include/ipc4/module.h index dfe0f02e628e..f026f837801a 100644 --- a/src/include/ipc4/module.h +++ b/src/include/ipc4/module.h @@ -93,9 +93,7 @@ struct ipc4_module_init_ext_object { struct ipc4_module_init_ext_obj_dp_data { uint32_t domain_id; /* userspace domain ID */ uint32_t stack_bytes; /* required stack size in bytes */ - uint32_t interim_heap_bytes; /* required interim heap size in bytes */ - uint32_t lifetime_heap_bytes; /* required lifetime heap size in bytes */ - uint32_t shared_bytes; /* required shared memory size in bytes */ + uint32_t heap_bytes; /* required heap size in bytes */ } __attribute__((packed, aligned(4))); /* @@ -153,6 +151,10 @@ struct ipc4_module_init_data { \remark hide_methods */ + +/* Reserved ppl_instance_id: module has no parent pipeline (e.g. probe). */ +#define IPC4_INVALID_PIPELINE_ID 0xFF + struct ipc4_module_init_instance { union { diff --git a/src/include/ipc4/pipeline.h b/src/include/ipc4/pipeline.h index 198918cf8577..1e85072fd15c 100644 --- a/src/include/ipc4/pipeline.h +++ b/src/include/ipc4/pipeline.h @@ -88,9 +88,7 @@ struct ipc4_pipeline_ext_object { struct ipc4_pipeline_ext_obj_mem_data { uint32_t domain_id; /* userspace domain ID */ uint32_t stack_bytes; /* required stack size in bytes */ - uint32_t interim_heap_bytes; /* required interim heap size in bytes */ - uint32_t lifetime_heap_bytes; /* required lifetime heap size in bytes */ - uint32_t shared_bytes; /* required shared memory in bytes */ + uint32_t heap_bytes; /* required heap size in bytes */ } __packed __aligned(4); /* diff --git a/src/include/module/audio/audio_stream.h b/src/include/module/audio/audio_stream.h index e032ef322f85..2d8308447d11 100644 --- a/src/include/module/audio/audio_stream.h +++ b/src/include/module/audio/audio_stream.h @@ -11,6 +11,7 @@ #include #include +#include #include "../ipc/stream.h" @@ -74,4 +75,146 @@ struct sof_audio_stream_params { enum sof_audio_buffer_state state; /**< audio stream state */ }; +/** + * @brief Read-only view of source data in a circular buffer. + * + * Describes a contiguous fragment of a circular buffer obtained from the source + * API together with the buffer boundaries needed for wrap handling. All pointers + * are const because the source data must not be modified. + */ +struct cir_buf_source { + const void *buf_start; /**< Start address of the circular buffer. */ + const void *buf_end; /**< End address of the circular buffer. */ + const void *ptr; /**< Current read pointer within the buffer. */ +}; + +/** + * @brief Writable view of sink data in a circular buffer. + * + * Describes a contiguous fragment of a circular buffer obtained from the sink + * API together with the buffer boundaries needed for wrap handling. + */ +struct cir_buf_sink { + void *buf_start; /**< Start address of the circular buffer. */ + void *buf_end; /**< End address of the circular buffer. */ + void *ptr; /**< Current write pointer within the buffer. */ +}; + +/** + * @brief Calculates numbers of s16 samples to buffer wrap. + * @param ptr Read or write pointer of circular buffer. + * @param buf_start Start address of circular buffer. + * @param buf_samples Total size of circular buffer in samples. + * @return Number of samples to buffer wrap. + */ +static inline size_t cir_buf_samples_to_wrap_s16(const int16_t *ptr, const int16_t *buf_start, + size_t buf_samples) +{ + const int16_t *const buf_end = buf_start + buf_samples; + + assert(buf_end >= ptr); + + return buf_end - ptr; +} + +/** + * @brief Calculates numbers of s32 samples to buffer wrap. + * @param ptr Read or write pointer of circular buffer. + * @param buf_start Start address of circular buffer. + * @param buf_samples Total size of circular buffer in samples. + * @return Number of samples to buffer wrap. + */ +static inline size_t cir_buf_samples_to_wrap_s32(const int32_t *ptr, const int32_t *buf_start, + size_t buf_samples) +{ + const int32_t *const buf_end = buf_start + buf_samples; + + assert(buf_end >= ptr); + + return buf_end - ptr; +} + +/** + * @brief Calculates numbers of s16 samples to buffer wrap when reading stream + * backwards from current sample pointed by ptr towards begin. + * @param ptr Read or write pointer of circular buffer. + * @param buf_end End address of circular buffer. + * @return Number of samples to buffer wrap. + */ +static inline int cir_buf_samples_without_wrap_s16(const void *ptr, const void *buf_end) +{ + int to_end = (const int16_t *)buf_end - (const int16_t *)ptr; + + assert((intptr_t)buf_end >= (intptr_t)ptr); + return to_end; +} + +/** + * @brief Calculates numbers of s32 samples to buffer wrap when reading stream + * backwards from current sample pointed by ptr towards begin. + * @param ptr Read or write pointer og circular buffer. + * @param buf_end End address of circular buffer. + * @return Number of bytes to buffer wrap. For number of samples calculate + * need to add size of sample to returned bytes count. + */ +static inline int cir_buf_samples_without_wrap_s32(const void *ptr, const void *buf_end) +{ + int to_end = (const int32_t *)buf_end - (const int32_t *)ptr; + + assert((intptr_t)buf_end >= (intptr_t)ptr); + return to_end; +} + +/** + * Verifies the pointer and performs rollover when reached the end of + * the circular buffer. + * @param ptr Pointer + * @param buf_addr Start address of the circular buffer. + * @param buf_end End address of the circular buffer. + * @return Pointer, adjusted if necessary. + */ +static inline void *cir_buf_wrap(const void *ptr, const void *buf_addr, const void *buf_end) +{ + if (ptr >= buf_end) + ptr = (const char *)buf_addr + + ((const char *)ptr - (const char *)buf_end); + + assert((intptr_t)ptr <= (intptr_t)buf_end); + + return (void *)ptr; +} + +/** + * @brief Calculates number of bytes to buffer wrap when reading a circular + * buffer backwards from current pointer towards the buffer start. + * @param ptr Read or write pointer of circular buffer. + * @param buf_start Start address of circular buffer. + * @return Number of bytes between the buffer start and the pointer. + */ +static inline int cir_buf_bytes_without_wrap_rewind(const void *ptr, const void *buf_start) +{ + assert((intptr_t)ptr >= (intptr_t)buf_start); + + return (intptr_t)ptr - (intptr_t)buf_start; +} + +/** + * @brief Verifies the pointer and performs rollover when reading a read-only circular + * buffer backwards past its start address. + * @param ptr Pointer that may have moved below the buffer start. + * @param buf_start Start address of the circular buffer. + * @param buf_end End address of the circular buffer. + * @return Pointer, wrapped to the end of the buffer if necessary. + */ +static inline const void *source_cir_buf_rewind_wrap(const void *ptr, const void *buf_start, + const void *buf_end) +{ + if (ptr < buf_start) + ptr = (const char *)buf_end - ((const char *)buf_start - (const char *)ptr); + + assert((intptr_t)ptr >= (intptr_t)buf_start); + + return ptr; +} + #endif /* __MODULE_AUDIO_AUDIO_STREAM_H__ */ diff --git a/src/include/module/audio/sink_api.h b/src/include/module/audio/sink_api.h index 920087d6f8b3..14c14af0187b 100644 --- a/src/include/module/audio/sink_api.h +++ b/src/include/module/audio/sink_api.h @@ -145,6 +145,17 @@ static inline size_t sink_get_free_size(struct sof_sink *sink) return sink->ops->get_free_size(sink); } +/** + * Retrieves number of free frames in sink aligned to the alignment constants + * set by sink_set_alignment_constants(). + * @return Number of aligned free frames. + */ +static inline size_t sink_get_free_frames_aligned(struct sof_sink *sink) +{ + return (sink_get_free_size(sink) >> sink->audio_stream_params->align_shift_idx) * + sink->audio_stream_params->align_frame_cnt; +} + static inline enum sof_ipc_frame sink_get_frm_fmt(struct sof_sink *sink) { return sink->audio_stream_params->frame_fmt; diff --git a/src/include/module/audio/source_api.h b/src/include/module/audio/source_api.h index 556dead4a583..ea4af5442249 100644 --- a/src/include/module/audio/source_api.h +++ b/src/include/module/audio/source_api.h @@ -12,6 +12,8 @@ #include #include +#include + #include "audio_stream.h" #include "format.h" @@ -140,6 +142,17 @@ static inline size_t source_get_data_available(struct sof_source *source) return source->ops->get_data_available(source); } +/** + * Retrieves number of available data frames aligned to the alignment constants + * set by source_set_alignment_constants(). + * @return Number of aligned available frames. + */ +static inline size_t source_get_aligned_frames_available(struct sof_source *source) +{ + return (source_get_data_available(source) >> source->audio_stream_params->align_shift_idx) * + source->audio_stream_params->align_frame_cnt; +} + static inline enum sof_ipc_frame source_get_frm_fmt(struct sof_source *source) { return source->audio_stream_params->frame_fmt; @@ -328,4 +341,34 @@ static inline enum sof_audio_buffer_state source_get_state(const struct sof_sour return source->audio_stream_params->state; } +static inline uint32_t source_align_frames_round_up(struct sof_source *source, uint32_t frames) +{ + uint16_t align = source->audio_stream_params->align_frame_cnt; + + if (align <= 1) + return frames; + + return ROUND_UP(frames, align); +} + +static inline uint32_t source_align_frames_round_down(struct sof_source *source, uint32_t frames) +{ + uint16_t align = source->audio_stream_params->align_frame_cnt; + + if (align <= 1) + return frames; + + return ROUND_DOWN(frames, align); +} + +static inline uint32_t source_align_frames_round_nearest(struct sof_source *source, uint32_t frames) +{ + uint16_t align = source->audio_stream_params->align_frame_cnt; + + if (!align) + return frames; + + return ROUND_DOWN(frames + (align >> 1), align); +} + #endif /* __MODULE_AUDIO_SOURCE_API_H__ */ diff --git a/src/include/module/module/base.h b/src/include/module/module/base.h index 657673099d60..8818ffb9f212 100644 --- a/src/include/module/module/base.h +++ b/src/include/module/module/base.h @@ -203,6 +203,13 @@ struct processing_module { struct userspace_context *user_ctx; struct k_mem_domain *mdom; #endif /* CONFIG_USERSPACE */ + + /* total size of a fragmented runtime-params (get/set) transfer, kept + * per instance so concurrent transfers to different components do not + * corrupt each other's reassembly state. Appended here to avoid shifting + * the offsets of the fields above. + */ + uint32_t runtime_params_size; #endif /* SOF_MODULE_PRIVATE */ }; diff --git a/src/include/module/module/interface.h b/src/include/module/module/interface.h index 26c176aee9b1..a968ab862028 100644 --- a/src/include/module/module/interface.h +++ b/src/include/module/module/interface.h @@ -239,13 +239,13 @@ struct module_interface { uint8_t *fragment, size_t fragment_size); /** - * (unused) Set processing mode for the module + * (IADK) Set processing mode for the module */ int (*set_processing_mode)(struct processing_module *mod, enum module_processing_mode mode); /** - * (unused) Get the current processing mode for the module + * (IADK) Get the current processing mode for the module */ enum module_processing_mode (*get_processing_mode)(struct processing_module *mod); diff --git a/src/include/sof/audio/audio_stream.h b/src/include/sof/audio/audio_stream.h index dd81c157b8d8..dc5d55103b4b 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) @@ -401,25 +410,6 @@ static inline void *audio_stream_wrap(const struct audio_stream *buffer, void *p return ptr; } -/** - * Verifies the pointer and performs rollover when reached the end of - * the circular buffer. - * @param ptr Pointer - * @param buf_addr Start address of the circular buffer. - * @param buf_end End address of the circular buffer. - * @return Pointer, adjusted if necessary. - */ -static inline void *cir_buf_wrap(void *ptr, void *buf_addr, void *buf_end) -{ - if (ptr >= buf_end) - ptr = (char *)buf_addr + - ((char *)ptr - (char *)buf_end); - - assert((intptr_t)ptr <= (intptr_t)buf_end); - - return ptr; -} - /** * Verifies the pointer and performs rollover when reached the end of * the buffer. @@ -918,22 +908,6 @@ static inline int cir_buf_bytes_without_wrap(void *ptr, void *buf_end) return (intptr_t)buf_end - (intptr_t)ptr; } -/** - * @brief Calculates numbers of s32 samples to buffer wrap when reading stream - * backwards from current sample pointed by ptr towards begin. - * @param ptr Read or write pointer og circular buffer. - * @param buf_end End address of circular buffer. - * @return Number of bytes to buffer wrap. For number of samples calculate - * need to add size of sample to returned bytes count. - */ -static inline int cir_buf_samples_without_wrap_s32(void *ptr, void *buf_end) -{ - int to_end = (int32_t *)buf_end - (int32_t *)ptr; - - assert((intptr_t)buf_end >= (intptr_t)ptr); - return to_end; -} - /** * @brief Calculates numbers of frames to buffer wrap and return * minimum of calculated value. diff --git a/src/include/sof/audio/buffer.h b/src/include/sof/audio/buffer.h index 6e6b8a9caef8..517f229e5420 100644 --- a/src/include/sof/audio/buffer.h +++ b/src/include/sof/audio/buffer.h @@ -289,8 +289,11 @@ static inline void buffer_stream_writeback(struct comp_buffer *buffer, uint32_t * really be the head of the list, not a list head within another buffer. We * don't synchronise its cache, so it must not be embedded in an object, using * the coherent API. The caller takes care to protect list heads. + * + * Returns -EINVAL if the buffer is already linked in this direction + * (re-attaching would create a self-loop and corrupt the list). */ -void buffer_attach(struct comp_buffer *buffer, struct list_item *head, int dir); +int buffer_attach(struct comp_buffer *buffer, struct list_item *head, int dir); /* * Detach a buffer from anywhere in the list. "head" is again the head of the diff --git a/src/include/sof/audio/component.h b/src/include/sof/audio/component.h index ebb88c8f79a1..db8a69e63646 100644 --- a/src/include/sof/audio/component.h +++ b/src/include/sof/audio/component.h @@ -23,7 +23,6 @@ #include #include #include -#include #include #include #include @@ -681,10 +680,6 @@ struct comp_dev { struct list_item bsource_list; /**< list of source buffers */ struct list_item bsink_list; /**< list of sink buffers */ -#ifdef CONFIG_SOF_USERSPACE_LL - struct sys_mutex list_mutex; /**< protect lists of source/sinks */ -#endif - /* performance data*/ struct comp_perf_data perf_data; /* Input Buffer Size for pin 0, add array for other pins if needed */ @@ -869,9 +864,6 @@ static inline void comp_init(const struct comp_driver *drv, dev->state = COMP_STATE_INIT; list_init(&dev->bsink_list); list_init(&dev->bsource_list); -#ifdef CONFIG_SOF_USERSPACE_LL - sys_mutex_init(&dev->list_mutex); -#endif #ifndef __ZEPHYR__ memcpy_s(&dev->tctx, sizeof(dev->tctx), trace_comp_drv_get_tr_ctx(dev->drv), sizeof(struct tr_ctx)); @@ -974,6 +966,7 @@ void sys_comp_module_mixout_interface_init(void); void sys_comp_module_multiband_drc_interface_init(void); void sys_comp_module_mux_interface_init(void); void sys_comp_module_nxp_eap_interface_init(void); +void sys_comp_module_phase_vocoder_interface_init(void); void sys_comp_module_rtnr_interface_init(void); void sys_comp_module_selector_interface_init(void); void sys_comp_module_sound_dose_interface_init(void); diff --git a/src/include/sof/audio/component_ext.h b/src/include/sof/audio/component_ext.h index d2bbf87a7764..313ff42ab588 100644 --- a/src/include/sof/audio/component_ext.h +++ b/src/include/sof/audio/component_ext.h @@ -26,7 +26,6 @@ /** \brief Holds list of registered components' drivers */ struct comp_driver_list { struct list_item list; /**< list of component drivers */ - struct k_spinlock lock; /**< list lock */ }; /** \brief Retrieves the component device buffer list. */ @@ -425,10 +424,18 @@ static inline void comp_make_shared(struct comp_dev *dev) dev->is_shared = true; } +/** + * Get the global component driver list. + * @return Pointer to the component driver list. + */ +#ifdef CONFIG_SOF_USERSPACE_LL +struct comp_driver_list *comp_drivers_get(void); +#else static inline struct comp_driver_list *comp_drivers_get(void) { return sof_get()->comp_drivers; } +#endif #if CONFIG_IPC_MAJOR_4 static inline int comp_ipc4_bind_remote(struct comp_dev *dev, struct bind_info *bind_data) diff --git a/src/include/sof/audio/compress_other/pcm_dec/xa_pcm_dec_api.h b/src/include/sof/audio/compress_other/pcm_dec/xa_pcm_dec_api.h new file mode 100644 index 000000000000..e79e1e85a200 --- /dev/null +++ b/src/include/sof/audio/compress_other/pcm_dec/xa_pcm_dec_api.h @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2026 Intel Corporation. + * + */ + +#ifndef __XA_PCM_DEC_API_H__ +#define __XA_PCM_DEC_API_H__ + +#include + +/*****************************************************************************/ +/* PCM Decoder specific API definitions */ +/*****************************************************************************/ + +/* pcm_dec-specific configuration parameters */ +enum xa_config_param_pcm_dec { + XA_PCM_DEC_CONFIG_PARAM_CHANNELS = 0, + XA_PCM_DEC_CONFIG_PARAM_SAMPLE_RATE = 1, + XA_PCM_DEC_CONFIG_PARAM_PCM_WIDTH = 2, + XA_PCM_DEC_CONFIG_PARAM_PRODUCED = 3, + XA_PCM_DEC_CONFIG_PARAM_INTERLEAVE = 5 +}; + +/* commands */ +#include + +/* error codes */ +#include + +#define XA_CODEC_PCM_DEC 15 + +/* pcm_dec-specific error codes */ +/*****************************************************************************/ +/* Class 1: Configuration Errors */ +/*****************************************************************************/ +/* Nonfatal Errors */ +enum xa_error_nonfatal_config_pcm_dec { + XA_PCMDEC_CONFIG_NONFATAL_INVALID_PCM_WIDTH = + XA_ERROR_CODE(xa_severity_nonfatal, xa_class_config, XA_CODEC_PCM_DEC, 2) +}; + +/*****************************************************************************/ +/* Class 2: Execution Errors */ +/*****************************************************************************/ +/* Nonfatal Errors */ +enum xa_error_nonfatal_execute_pcm_dec { + XA_PCMDEC_EXECUTE_NONFATAL_INSUFFICIENT_DATA = + XA_ERROR_CODE(xa_severity_nonfatal, xa_class_execute, XA_CODEC_PCM_DEC, 0) +}; + +/* Fatal Errors */ +#define XA_PCMDEC_EXECUTE_FATAL_UNINITIALIZED \ + XA_ERROR_CODE((uint32_t)xa_severity_fatal, xa_class_execute, XA_CODEC_PCM_DEC, 0) + +#endif /* __XA_PCM_DEC_API_H__ */ diff --git a/src/include/sof/audio/intel_uaol.h b/src/include/sof/audio/intel_uaol.h new file mode 100644 index 000000000000..e8964d2d7450 --- /dev/null +++ b/src/include/sof/audio/intel_uaol.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright 2026 Intel Corporation. All rights reserved. + */ + +#ifndef __SOF_AUDIO_INTEL_UAOL_H__ +#define __SOF_AUDIO_INTEL_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_INTEL_UAOL_H__ */ diff --git a/src/include/sof/audio/mfcc/mfcc_comp.h b/src/include/sof/audio/mfcc/mfcc_comp.h index 4e41c8a4df08..885339004fc0 100644 --- a/src/include/sof/audio/mfcc/mfcc_comp.h +++ b/src/include/sof/audio/mfcc/mfcc_comp.h @@ -54,18 +54,6 @@ struct mfcc_data_header { int32_t vad_flag; /**< VAD decision: 1 = speech, 0 = silence */ }; -/** \brief Type definition for processing function select return value. */ -typedef void (*mfcc_func)(struct processing_module *mod, - struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, - int frames); - -/** \brief MFCC processing functions map item. */ -struct mfcc_func_map { - uint8_t source; /**< source frame format */ - mfcc_func func; /**< processing function */ -}; - struct mfcc_buffer { int16_t *addr; int16_t *end_addr; @@ -82,6 +70,10 @@ struct mfcc_pre_emph { int enable; }; +/** \brief Type definition for source/sink based input copy function. */ +typedef void (*mfcc_source_func)(struct sof_source *source, struct mfcc_buffer *buf, + struct mfcc_pre_emph *emph, int frames, int source_channel); + struct mfcc_fft { struct icomplex32 *fft_buf; /**< fft_padded_size */ struct icomplex32 *fft_out; /**< fft_padded_size */ @@ -130,10 +122,15 @@ struct mfcc_state { bool header_pending; /**< True when data header not yet written for current output */ struct mfcc_data_header header; /**< Data header for current output frame */ size_t sample_buffers_size; /**< bytes */ - int16_t *out_data_ptr; /**< Read pointer into scratch data for multi-period output */ - int32_t *out_data_ptr_32; /**< Read pointer for 32-bit mel-only output */ - int out_remain; /**< Remaining int16_t samples to write to sink from scratch */ + int32_t *out_data_ptr; /**< Read pointer into staging data for multi-period output */ + int out_remain; /**< Remaining int32_t samples to write to sink from staging */ + int32_t *out_stage; /**< Dedicated staging buffer for pending output, decoupled from STFT scratch */ + int out_stage_size; /**< Capacity of out_stage in int32_t samples */ uint32_t hop_count; /**< FFT hop counter, increments every processed hop */ + int vad_silence_count; /**< Consecutive VAD=0 hops since last speech */ + int16_t dtx_trailing_silence; /**< Number of trailing silence hops to send, from config */ + int16_t dtx_silence_interval; /**< Send silence frame every Nth hop, 0 = disable */ + int dtx_silence_counter; /**< Counter for periodic silence frame send */ }; /* MFCC component private data */ @@ -144,8 +141,9 @@ struct mfcc_comp_data { struct sof_mfcc_config *config; struct ipc_msg *msg; /**< IPC notification for VAD switch control */ int max_frames; + enum sof_ipc_frame source_format; /**< Source audio format for output sizing */ bool vad_prev; /**< Previous VAD state for edge detection */ - mfcc_func mfcc_func; /**< processing function */ + mfcc_source_func source_func; /**< source copy function */ }; static inline int mfcc_buffer_samples_without_wrap(struct mfcc_buffer *buffer, int16_t *ptr) @@ -172,31 +170,37 @@ void mfcc_fill_fft_buffer(struct mfcc_state *state); void mfcc_apply_window(struct mfcc_state *state, int input_shift); -#if CONFIG_FORMAT_S16LE +/** + * \brief Run STFT and Mel/DCT processing. + * \return Number of output coefficients produced, or 0 if not enough data. + */ +int mfcc_stft_process(struct processing_module *mod, struct mfcc_comp_data *cd); -void mfcc_source_copy_s16(struct input_stream_buffer *bsource, struct mfcc_buffer *buf, - struct mfcc_pre_emph *emph, int frames, int source_channel); +/** + * \brief Prepare and commit MFCC output data after STFT processing. + * + * This handles the output data conversion and dispatches to either the + * compress-output or legacy PCM-output path. + * + * \return 0 on success or a negative error code. + */ +int mfcc_process_output(struct processing_module *mod, struct mfcc_comp_data *cd, + struct sof_source **sources, struct sof_sink **sinks, + int num_ceps, int frames); -void mfcc_s16_default(struct processing_module *mod, struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, int frames); +#if CONFIG_FORMAT_S16LE +void mfcc_source_copy_s16(struct sof_source *source, struct mfcc_buffer *buf, + struct mfcc_pre_emph *emph, int frames, int source_channel); #endif #if CONFIG_FORMAT_S24LE - -void mfcc_source_copy_s24(struct input_stream_buffer *bsource, struct mfcc_buffer *buf, +void mfcc_source_copy_s24(struct sof_source *source, struct mfcc_buffer *buf, struct mfcc_pre_emph *emph, int frames, int source_channel); - -void mfcc_s24_default(struct processing_module *mod, struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, int frames); #endif #if CONFIG_FORMAT_S32LE - -void mfcc_source_copy_s32(struct input_stream_buffer *bsource, struct mfcc_buffer *buf, +void mfcc_source_copy_s32(struct sof_source *source, struct mfcc_buffer *buf, struct mfcc_pre_emph *emph, int frames, int source_channel); - -void mfcc_s32_default(struct processing_module *mod, struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, int frames); #endif #if CONFIG_IPC_MAJOR_4 diff --git a/src/include/sof/audio/module_adapter/module/cadence.h b/src/include/sof/audio/module_adapter/module/cadence.h index 8ba658749c05..ccca668bf76a 100644 --- a/src/include/sof/audio/module_adapter/module/cadence.h +++ b/src/include/sof/audio/module_adapter/module/cadence.h @@ -32,6 +32,7 @@ extern xa_codec_func_t xa_mp3_enc; extern xa_codec_func_t xa_sbc_dec; extern xa_codec_func_t xa_vorbis_dec; extern xa_codec_func_t xa_src_pp; +extern xa_codec_func_t xa_pcm_dec; #define DEFAULT_CODEC_ID CADENCE_CODEC_WRAPPER_ID @@ -51,10 +52,13 @@ struct cadence_api { xa_codec_func_t *api; }; +struct ipc_msg; + struct cadence_codec_data { #if CONFIG_IPC_MAJOR_4 struct ipc4_base_module_cfg base_cfg; uint32_t direction; + struct ipc_msg *msg; #endif char name[LIB_NAME_MAX_LEN]; void *self; @@ -77,6 +81,12 @@ enum cadence_api_id { CADENCE_CODEC_VORBIS_DEC_ID = 0x08, CADENCE_CODEC_SRC_PP_ID = 0x09, CADENCE_CODEC_MP3_ENC_ID = 0x0A, + /* + * IDs >= 0x80 are reserved for in-tree open-source codecs that + * plug into the Cadence wrapper but are not Cadence proprietary + * codecs. + */ + SOF_COMPRESS_CODEC_PCM_DEC_ID = 0xC0, }; #if CONFIG_IPC_MAJOR_4 @@ -101,11 +111,16 @@ int cadence_codec_process_data(struct processing_module *mod, int cadence_codec_apply_config(struct processing_module *mod); void cadence_codec_free_memory_tables(struct processing_module *mod); int cadence_codec_init_memory_tables(struct processing_module *mod); -int cadence_codec_get_samples(struct processing_module *mod); +unsigned int cadence_codec_get_samples(struct processing_module *mod); int cadence_codec_init_process(struct processing_module *mod); int cadence_init_codec_object(struct processing_module *mod); int cadence_codec_resolve_api(struct processing_module *mod); int cadence_codec_free(struct processing_module *mod); size_t cadence_api_table_size(void); +void cadence_copy_data_from_buffer(void *dest, const void *buffer_ptr, size_t bytes_to_copy, + size_t buffer_size, void const *buffer_start); +void cadence_copy_data_to_buffer(void *buffer_ptr, size_t bytes_to_copy, + size_t buffer_size, void *buffer_start, + const void *src); #endif /* __SOF_AUDIO_CADENCE_CODEC__ */ diff --git a/src/include/sof/audio/module_adapter/module/generic.h b/src/include/sof/audio/module_adapter/module/generic.h index 94827d86cd9f..6740593a7cf1 100644 --- a/src/include/sof/audio/module_adapter/module/generic.h +++ b/src/include/sof/audio/module_adapter/module/generic.h @@ -13,6 +13,7 @@ #ifndef __SOF_AUDIO_MODULE_GENERIC__ #define __SOF_AUDIO_MODULE_GENERIC__ +#include #include #include #include @@ -20,10 +21,6 @@ #include #include "module_interface.h" -/* The __ZEPHYR__ condition is to keep cmocka tests working */ -#if CONFIG_MODULE_MEMORY_API_DEBUG && defined(__ZEPHYR__) -#include -#endif #include /* @@ -87,8 +84,7 @@ static SHARED_DATA struct comp_driver_info comp_module_##adapter##_info = { \ \ UT_STATIC void sys_comp_module_##adapter##_init(void) \ { \ - comp_register(platform_shared_get(&comp_module_##adapter##_info, \ - sizeof(comp_module_##adapter##_info))); \ + comp_register(&comp_module_##adapter##_info); \ } \ \ DECLARE_MODULE(sys_comp_module_##adapter##_init) @@ -129,13 +125,11 @@ struct module_param { * when the module unloads. */ struct module_resources { + struct k_mutex lock; struct objpool_head objpool; size_t heap_usage; size_t heap_high_water_mark; struct mod_alloc_ctx *alloc; -#if CONFIG_MODULE_MEMORY_API_DEBUG && defined(__ZEPHYR__) - k_tid_t rsrc_mngr; -#endif }; enum mod_resource_type { @@ -191,7 +185,12 @@ struct module_processing_data { /*****************************************************************************/ int module_load_config(struct comp_dev *dev, const void *cfg, size_t size); int module_init(struct processing_module *mod); -void *mod_balloc_align(struct processing_module *mod, size_t size, size_t alignment); +#if defined(__ZEPHYR__) && defined(CONFIG_SOF_FULL_ZEPHYR_APPLICATION) +__syscall void *mod_balloc_align(struct processing_module *mod, size_t size, size_t alignment); +#else +void *z_impl_mod_balloc_align(struct processing_module *mod, size_t size, size_t alignment); +#define mod_balloc_align z_impl_mod_balloc_align +#endif void mod_resource_init(struct processing_module *mod); void mod_heap_info(struct processing_module *mod, size_t *size, uintptr_t *start); #if defined(__ZEPHYR__) && defined(CONFIG_SOF_FULL_ZEPHYR_APPLICATION) @@ -241,7 +240,12 @@ static inline void *mod_zalloc(struct processing_module *mod, size_t size) } #if CONFIG_COMP_BLOB -struct comp_data_blob_handler *mod_data_blob_handler_new(struct processing_module *mod); +#if defined(__ZEPHYR__) && defined(CONFIG_SOF_FULL_ZEPHYR_APPLICATION) +__syscall struct comp_data_blob_handler *mod_data_blob_handler_new(struct processing_module *mod); +#else +struct comp_data_blob_handler *z_impl_mod_data_blob_handler_new(struct processing_module *mod); +#define mod_data_blob_handler_new z_impl_mod_data_blob_handler_new +#endif void mod_data_blob_handler_free(struct processing_module *mod, struct comp_data_blob_handler *dbh); #endif #if CONFIG_FAST_GET diff --git a/src/include/sof/audio/selector.h b/src/include/sof/audio/selector.h index e977a47af9a9..fd413badab7f 100644 --- a/src/include/sof/audio/selector.h +++ b/src/include/sof/audio/selector.h @@ -32,6 +32,7 @@ struct comp_dev; #if CONFIG_IPC_MAJOR_3 /** \brief Supported channel count on input. */ +#define SEL_SOURCE_1CH 1 #define SEL_SOURCE_2CH 2 #define SEL_SOURCE_4CH 4 diff --git a/src/include/sof/audio/sink_source_utils.h b/src/include/sof/audio/sink_source_utils.h index 818f29444c22..2d31666ac383 100644 --- a/src/include/sof/audio/sink_source_utils.h +++ b/src/include/sof/audio/sink_source_utils.h @@ -9,6 +9,7 @@ #include #include +#include /** * copy bytes from source to sink @@ -38,4 +39,23 @@ int sink_fill_with_silence(struct sof_sink *sink, size_t size); */ int source_drop_data(struct sof_source *source, size_t size); +/** + * Computes maximum number of frames aligned with the source align criteria + * that can be copied from source to sink, verifying the number of available + * source frames vs. free space available in sink. + * + * @param source the data source + * @param sink the data sink + * @return Number of aligned frames available for processing. + */ +static inline size_t source_sink_avail_frames_aligned(struct sof_source *source, + struct sof_sink *sink) +{ + size_t src_frames = source_get_data_frames_available(source); + size_t sink_frames = sink_get_free_frames(sink); + size_t n = MIN(src_frames, sink_frames); + + return source_align_frames_round_down(source, n); +} + #endif /* SINK_SOURCE_UTILS_H */ diff --git a/src/include/sof/boot_test.h b/src/include/sof/boot_test.h index 1af6e7f2d8e0..dfa8671ee55f 100644 --- a/src/include/sof/boot_test.h +++ b/src/include/sof/boot_test.h @@ -13,6 +13,8 @@ #endif #include +struct k_thread; + #if CONFIG_SOF_BOOT_TEST #define TEST_RUN_ONCE(fn, ...) do { \ static bool once; \ @@ -36,4 +38,18 @@ void sof_run_boot_tests(void); +/** + * Mark a boot-test thread as expected to trigger a fatal error. + * + * @param thread Thread that is allowed to fault once, or NULL to clear. + */ +#if CONFIG_SOF_BOOT_TEST +void sof_boot_test_set_fault_valid(struct k_thread *thread); +#else +static inline void sof_boot_test_set_fault_valid(struct k_thread *thread) +{ + (void)thread; +} +#endif + #endif diff --git a/src/include/sof/ctx_alloc.h b/src/include/sof/ctx_alloc.h deleted file mode 100644 index 389c16a27507..000000000000 --- a/src/include/sof/ctx_alloc.h +++ /dev/null @@ -1,81 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * - * Copyright(c) 2026 Intel Corporation. All rights reserved. - */ - -#ifndef __SOF_CTX_ALLOC_H__ -#define __SOF_CTX_ALLOC_H__ - -#include -#include -#include -#include -#include - -struct mod_alloc_ctx { - struct k_heap *heap; - struct vregion *vreg; -}; - -/** - * Allocate memory from a mod_alloc_ctx context. - * - * When the context has a vregion, allocates from the vregion interim - * partition. Coherent memory is used when SOF_MEM_FLAG_COHERENT is set - * in flags. Falls back to sof_heap_alloc() otherwise. - * - * @param ctx Allocation context (heap + optional vregion). - * @param flags Allocation flags (SOF_MEM_FLAG_*). - * @param size Size in bytes. - * @param alignment Required alignment in bytes. - * @return Pointer to allocated memory or NULL on failure. - */ -static inline void *sof_ctx_alloc(struct mod_alloc_ctx *ctx, uint32_t flags, - size_t size, size_t alignment) -{ - if (!ctx || !ctx->vreg) - return sof_heap_alloc(ctx ? ctx->heap : NULL, flags, size, alignment); - - if (flags & SOF_MEM_FLAG_COHERENT) - return vregion_alloc_coherent_align(ctx->vreg, VREGION_MEM_TYPE_INTERIM, - size, alignment); - - return vregion_alloc_align(ctx->vreg, VREGION_MEM_TYPE_INTERIM, size, alignment); -} - -/** - * Allocate zero-initialized memory from a mod_alloc_ctx context. - * @param ctx Allocation context. - * @param flags Allocation flags (SOF_MEM_FLAG_*). - * @param size Size in bytes. - * @param alignment Required alignment in bytes. - * @return Pointer to allocated memory or NULL on failure. - */ -static inline void *sof_ctx_zalloc(struct mod_alloc_ctx *ctx, uint32_t flags, - size_t size, size_t alignment) -{ - void *ptr = sof_ctx_alloc(ctx, flags, size, alignment); - - if (ptr) - memset(ptr, 0, size); - - return ptr; -} - -/** - * Free memory allocated from a mod_alloc_ctx context. - * @param ctx Allocation context. - * @param ptr Pointer to free. - */ -static inline void sof_ctx_free(struct mod_alloc_ctx *ctx, void *ptr) -{ - if (!ptr) - return; - - if (ctx && ctx->vreg) - vregion_free(ctx->vreg, ptr); - else - sof_heap_free(ctx ? ctx->heap : NULL, ptr); -} - -#endif /* __SOF_CTX_ALLOC_H__ */ diff --git a/src/include/sof/ipc/topology.h b/src/include/sof/ipc/topology.h index 3503c7a407d8..84df84abb069 100644 --- a/src/include/sof/ipc/topology.h +++ b/src/include/sof/ipc/topology.h @@ -56,6 +56,8 @@ 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_all_dma_configs_tlvs_only(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/include/sof/lib/ams.h b/src/include/sof/lib/ams.h index 8cb3ae217400..e43d6208f875 100644 --- a/src/include/sof/lib/ams.h +++ b/src/include/sof/lib/ams.h @@ -29,10 +29,6 @@ /* Space allocated for async message content*/ #define AMS_MAX_MSG_SIZE 0x1000 -/* Size of slots message, module id and instance id */ -#define AMS_SLOT_SIZE(msg) (AMS_MESSAGE_SIZE(msg) + sizeof(uint16_t) * 2) -#define AMS_MESSAGE_SIZE(msg) (sizeof(*msg) - sizeof(char) + (sizeof(char) * (msg->message_length))) - /** * \brief IXC message payload * diff --git a/src/include/sof/lib/dai-zephyr.h b/src/include/sof/lib/dai-zephyr.h index 595d11de9b47..a0c42bff351e 100644 --- a/src/include/sof/lib/dai-zephyr.h +++ b/src/include/sof/lib/dai-zephyr.h @@ -26,7 +26,6 @@ #include #include #include -#include #include #include #include @@ -310,7 +309,7 @@ void dai_release_llp_slot(struct dai_data *dd); /** * \brief Retrieve a pointer to the Zephyr device structure for a DAI of a given type and index. */ -const struct device *dai_get_device(enum sof_ipc_dai_type type, uint32_t index); +__syscall const struct device *dai_get_device(enum sof_ipc_dai_type type, uint32_t index); /** * \brief Retrieve the list of all DAI devices. @@ -320,4 +319,6 @@ const struct device *dai_get_device(enum sof_ipc_dai_type type, uint32_t index); const struct device **dai_get_device_list(size_t *count); /** @}*/ +#include + #endif /* __SOF_LIB_DAI_ZEPHYR_H__ */ diff --git a/src/include/sof/lib/vregion.h b/src/include/sof/lib/vregion.h index 612443f5bc48..5c066c90dbc8 100644 --- a/src/include/sof/lib/vregion.h +++ b/src/include/sof/lib/vregion.h @@ -24,6 +24,7 @@ struct vregion; enum vregion_mem_type { VREGION_MEM_TYPE_INTERIM, /* interim allocation that can be freed */ VREGION_MEM_TYPE_LIFETIME, /* lifetime allocation */ + VREGION_MEM_TYPE_INVALID, /* Interim heap initialization failed */ }; #if CONFIG_SOF_VREGIONS @@ -31,14 +32,24 @@ enum vregion_mem_type { /** * @brief Create a new virtual region instance. * - * Create a new virtual region instance with specified static and dynamic partitions. - * Total size is the sum of static and dynamic sizes. + * Create a new virtual region instance with specified memory size. + * Allocations start in LIFETIME mode. * - * @param[in] lifetime_size Size of the virtual region lifetime partition. - * @param[in] interim_size Size of the virtual region interim partition. + * @param[in] memsize Total size of the virtual region memory. * @return struct vregion* Pointer to the new virtual region instance, or NULL on failure. */ -struct vregion *vregion_create(size_t lifetime_size, size_t interim_size); +struct vregion *vregion_create(size_t memsize); + +/** + * @brief Switch virtual region allocations to interim mode. + * + * After this call, all allocations from this vregion will use the interim + * heap. The interim heap is created lazily from remaining lifetime space. + * Multiple calls are allowed but log a warning. + * + * @param[in] vr Pointer to the virtual region instance. + */ +void vregion_set_interim(struct vregion *vr); /** * @brief Increment virtual region's user count. @@ -66,36 +77,33 @@ struct vregion *vregion_put(struct vregion *vr); * @brief Allocate memory from the specified virtual region. * * @param[in] vr Pointer to the virtual region instance. - * @param[in] type Type of memory to allocate (lifetime or interim). * @param[in] size Size of memory to allocate in bytes. * @return void* Pointer to the allocated memory, or NULL on failure. */ -void *vregion_alloc(struct vregion *vr, enum vregion_mem_type type, size_t size); +void *vregion_alloc(struct vregion *vr, size_t size); /** * @brief like vregion_alloc() but allocates coherent memory */ -void *vregion_alloc_coherent(struct vregion *vr, enum vregion_mem_type type, size_t size); +void *vregion_alloc_coherent(struct vregion *vr, size_t size); /** * @brief Allocate aligned memory from the specified virtual region. * - * Allocate aligned memory from the specified virtual region based on the memory type. + * Allocate aligned memory from the specified virtual region using the + * current allocation mode (lifetime or interim). * * @param[in] vr Pointer to the virtual region instance. - * @param[in] type Type of memory to allocate (lifetime or interim). * @param[in] size Size of memory to allocate in bytes. * @param[in] alignment Alignment of memory to allocate in bytes. * @return void* Pointer to the allocated memory, or NULL on failure. */ -void *vregion_alloc_align(struct vregion *vr, enum vregion_mem_type type, - size_t size, size_t alignment); +void *vregion_alloc_align(struct vregion *vr, size_t size, size_t alignment); /** * @brief like vregion_alloc_align() but allocates coherent memory */ -void *vregion_alloc_coherent_align(struct vregion *vr, enum vregion_mem_type type, - size_t size, size_t alignment); +void *vregion_alloc_coherent_align(struct vregion *vr, size_t size, size_t alignment); /** * @brief Free memory allocated from the specified virtual region. @@ -125,47 +133,37 @@ void vregion_mem_info(struct vregion *vr, size_t *size, uintptr_t *start); #else /* CONFIG_SOF_VREGIONS */ -#include - struct vregion { unsigned int use_count; }; -static inline struct vregion *vregion_create(size_t lifetime_size, size_t interim_size) +static inline struct vregion *vregion_create(size_t memsize) { - struct vregion *vr = rmalloc(0, sizeof(*vr)); - - vr->use_count = 1; - return vr; + return NULL; } +static inline void vregion_set_interim(struct vregion *vr) {} static inline struct vregion *vregion_get(struct vregion *vr) { - if (vr) - vr->use_count++; return vr; } static inline struct vregion *vregion_put(struct vregion *vr) { - if (vr && !--vr->use_count) - rfree(vr); return vr; } -static inline void *vregion_alloc(struct vregion *vr, enum vregion_mem_type type, size_t size) +static inline void *vregion_alloc(struct vregion *vr, size_t size) { return NULL; } -static inline void *vregion_alloc_coherent(struct vregion *vr, enum vregion_mem_type type, - size_t size) +static inline void *vregion_alloc_coherent(struct vregion *vr, size_t size) { return NULL; } -static inline void *vregion_alloc_align(struct vregion *vr, enum vregion_mem_type type, - size_t size, size_t alignment) +static inline void *vregion_alloc_align(struct vregion *vr, size_t size, size_t alignment) { return NULL; } -static inline void *vregion_alloc_coherent_align(struct vregion *vr, enum vregion_mem_type type, - size_t size, size_t alignment) +static inline void *vregion_alloc_coherent_align(struct vregion *vr, size_t size, + size_t alignment) { return NULL; } diff --git a/src/include/sof/math/auditory.h b/src/include/sof/math/auditory.h index bd707dc5079a..a68f0468d591 100644 --- a/src/include/sof/math/auditory.h +++ b/src/include/sof/math/auditory.h @@ -14,6 +14,7 @@ #include #include +#define AUDITORY_MAX_MEL_BANDS 256 #define AUDITORY_EPS_Q31 1 /* Smallest nonzero Q1.31 value */ #define AUDITORY_LOG2_2P25_Q16 Q_CONVERT_FLOAT(25.0, 16) /* log2(2^25) */ diff --git a/src/include/sof/math/numbers.h b/src/include/sof/math/numbers.h index 80d4eba1a639..d531b84a81fa 100644 --- a/src/include/sof/math/numbers.h +++ b/src/include/sof/math/numbers.h @@ -24,6 +24,12 @@ __typeof__(alignment) __alignment = (alignment); \ __size - (__size % __alignment); \ }) + +#define ROUND_UP(size, alignment) ({ \ + __typeof__(size) __size = (size); \ + __typeof__(alignment) __alignment = (alignment); \ + ((__size + __alignment - 1) / __alignment) * __alignment; \ +}) #endif /* ! __ZEPHYR__ */ #define ABS(a) ({ \ diff --git a/src/include/sof/schedule/ll_schedule_domain.h b/src/include/sof/schedule/ll_schedule_domain.h index 451ad7739f8f..9b0fd46b371e 100644 --- a/src/include/sof/schedule/ll_schedule_domain.h +++ b/src/include/sof/schedule/ll_schedule_domain.h @@ -47,6 +47,20 @@ struct ll_schedule_domain_ops { void (*handler)(void *arg), void *arg); int (*domain_unregister)(struct ll_schedule_domain *domain, struct task *task, uint32_t num_tasks); +#if CONFIG_SOF_USERSPACE_LL + /* + * Initialize the scheduling thread and perform all privileged setup + * (thread creation, timer init, access grants). Called once from + * kernel context before any user-space domain_register() calls. + */ + int (*domain_thread_init)(struct ll_schedule_domain *domain, + struct task *task); + /* Free resources acquired by domain_thread_init(). Called from + * kernel context when the scheduling context is being torn down. + */ + void (*domain_thread_free)(struct ll_schedule_domain *domain, + uint32_t num_tasks); +#endif void (*domain_enable)(struct ll_schedule_domain *domain, int core); void (*domain_disable)(struct ll_schedule_domain *domain, int core); #if CONFIG_CROSS_CORE_STREAM @@ -104,7 +118,16 @@ static inline struct ll_schedule_domain *dma_domain_get(void) #ifdef CONFIG_SOF_USERSPACE_LL struct task *zephyr_ll_task_alloc(void); struct k_heap *zephyr_ll_user_heap(void); +bool zephyr_ll_user_heap_verify(struct k_heap *heap); void zephyr_ll_user_resources_init(void); +void user_ll_lock_sched(int core); +void user_ll_unlock_sched(int core); +#ifdef CONFIG_ASSERT +/* Assert that the calling context already holds the LL lock for 'core'. */ +void user_ll_assert_locked(int core); +#else +static inline void user_ll_assert_locked(int core) { ARG_UNUSED(core); } +#endif #endif /* CONFIG_SOF_USERSPACE_LL */ static inline struct ll_schedule_domain *domain_init @@ -181,6 +204,31 @@ static inline void domain_task_cancel(struct ll_schedule_domain *domain, domain->ops->domain_task_cancel(domain, task); } +#if CONFIG_SOF_USERSPACE_LL +/* + * Initialize the scheduling thread and do all privileged setup. + * Must be called from kernel context before user-space tasks register. + */ +static inline int domain_thread_init(struct ll_schedule_domain *domain, + struct task *task) +{ + assert(domain->ops->domain_thread_init); + + return domain->ops->domain_thread_init(domain, task); +} + +/* + * Free resources acquired by domain_thread_init(). + * Must be called from kernel context. + */ +static inline void domain_thread_free(struct ll_schedule_domain *domain, + uint32_t num_tasks) +{ + if (domain->ops->domain_thread_free) + domain->ops->domain_thread_free(domain, num_tasks); +} +#endif + static inline int domain_register(struct ll_schedule_domain *domain, struct task *task, void (*handler)(void *arg), void *arg) diff --git a/src/include/sof/schedule/schedule.h b/src/include/sof/schedule/schedule.h index bbdcbbecf3b4..a68169736ef2 100644 --- a/src/include/sof/schedule/schedule.h +++ b/src/include/sof/schedule/schedule.h @@ -140,6 +140,7 @@ struct scheduler_ops { */ int (*schedule_task_free)(void *data, struct task *task); +#if CONFIG_SOF_BOOT_TEST_STANDALONE || CONFIG_LIBRARY /** * Frees scheduler's resources. * @param data Private data of selected scheduler. @@ -149,15 +150,17 @@ struct scheduler_ops { * This operation is optional. */ void (*scheduler_free)(void *data, uint32_t flags); +#endif /** - * Restores scheduler's resources. + * Initializes context * @param data Private data of selected scheduler. - * @return 0 if succeeded, error code otherwise. + * @param task task that needs to be scheduled + * @return thread that will be used to run the scheduled task * * This operation is optional. */ - int (*scheduler_restore)(void *data); + struct k_thread *(*scheduler_init_context)(void *data, struct task *task); }; /** \brief Holds information about scheduler. */ @@ -179,6 +182,10 @@ struct schedulers { */ struct schedulers **arch_schedulers_get(void); +struct schedulers **arch_user_schedulers_get(void); + +struct schedulers **arch_user_schedulers_get_for_core(int core); + /** * Retrieves scheduler's data. * @param type SOF_SCHEDULE_ type. @@ -190,6 +197,9 @@ static inline void *scheduler_get_data(uint16_t type) struct schedule_data *sch; struct list_item *slist; + if (!schedulers) + return NULL; + list_for_item(slist, &schedulers->list) { sch = container_of(slist, struct schedule_data, list); if (type == sch->type) @@ -202,151 +212,110 @@ static inline void *scheduler_get_data(uint16_t type) /** See scheduler_ops::schedule_task_running */ static inline int schedule_task_running(struct task *task) { - struct schedulers *schedulers = *arch_schedulers_get(); struct schedule_data *sch; - struct list_item *slist; - list_for_item(slist, &schedulers->list) { - sch = container_of(slist, struct schedule_data, list); - if (task->type == sch->type) { - /* optional operation */ - if (!sch->ops->schedule_task_running) - return 0; + assert(task); + sch = task->sch; - return sch->ops->schedule_task_running(sch->data, task); - } - } + if (!sch->ops->schedule_task_running) + return 0; - return -ENODEV; + return sch->ops->schedule_task_running(sch->data, task); } /** See scheduler_ops::schedule_task */ static inline int schedule_task(struct task *task, uint64_t start, uint64_t period) { - struct schedulers *schedulers = *arch_schedulers_get(); struct schedule_data *sch; - struct list_item *slist; if (!task) return -EINVAL; - list_for_item(slist, &schedulers->list) { - sch = container_of(slist, struct schedule_data, list); - if (task->type == sch->type) - return sch->ops->schedule_task(sch->data, task, start, - period); - } + sch = task->sch; - return -ENODEV; + return sch->ops->schedule_task(sch->data, task, start, period); } /** See scheduler_ops::schedule_task_before */ static inline int schedule_task_before(struct task *task, uint64_t start, uint64_t period, struct task *before) { - struct schedulers *schedulers = *arch_schedulers_get(); struct schedule_data *sch; - struct list_item *slist; if (!task || !before) return -EINVAL; - list_for_item(slist, &schedulers->list) { - sch = container_of(slist, struct schedule_data, list); - if (task->type == sch->type) { - if (sch->ops->schedule_task_before) - return sch->ops->schedule_task_before(sch->data, task, start, - period, before); - - return sch->ops->schedule_task(sch->data, task, start, - period); - } - } + sch = task->sch; + + if (sch->ops->schedule_task_before) + return sch->ops->schedule_task_before(sch->data, task, start, + period, before); - return -ENODEV; + return sch->ops->schedule_task(sch->data, task, start, period); } /** See scheduler_ops::schedule_task_after */ static inline int schedule_task_after(struct task *task, uint64_t start, uint64_t period, struct task *after) { - struct schedulers *schedulers = *arch_schedulers_get(); struct schedule_data *sch; - struct list_item *slist; if (!task || !after) return -EINVAL; - list_for_item(slist, &schedulers->list) { - sch = container_of(slist, struct schedule_data, list); - if (task->type == sch->type) { - if (sch->ops->schedule_task_after) - return sch->ops->schedule_task_after(sch->data, task, start, - period, after); - - return sch->ops->schedule_task(sch->data, task, start, - period); - } - } + sch = task->sch; + + if (sch->ops->schedule_task_after) + return sch->ops->schedule_task_after(sch->data, task, start, + period, after); - return -ENODEV; + return sch->ops->schedule_task(sch->data, task, start, period); } /** See scheduler_ops::reschedule_task */ static inline int reschedule_task(struct task *task, uint64_t start) { - struct schedulers *schedulers = *arch_schedulers_get(); struct schedule_data *sch; - struct list_item *slist; - list_for_item(slist, &schedulers->list) { - sch = container_of(slist, struct schedule_data, list); - if (task->type == sch->type) { - /* optional operation */ - if (!sch->ops->reschedule_task) - return 0; - - return sch->ops->reschedule_task(sch->data, task, - start); - } - } + assert(task); + sch = task->sch; + + /* optional operation */ + if (!sch->ops->reschedule_task) + return 0; - return -ENODEV; + return sch->ops->reschedule_task(sch->data, task, start); } /** See scheduler_ops::schedule_task_cancel */ static inline int schedule_task_cancel(struct task *task) { - struct schedulers *schedulers = *arch_schedulers_get(); struct schedule_data *sch; - struct list_item *slist; - list_for_item(slist, &schedulers->list) { - sch = container_of(slist, struct schedule_data, list); - if (task->type == sch->type) - return sch->ops->schedule_task_cancel(sch->data, task); - } + if (!task || !task->sch) + return -EINVAL; + + sch = task->sch; - return -ENODEV; + return sch->ops->schedule_task_cancel(sch->data, task); } /** See scheduler_ops::schedule_task_free */ static inline int schedule_task_free(struct task *task) { - struct schedulers *schedulers = *arch_schedulers_get(); struct schedule_data *sch; - struct list_item *slist; - list_for_item(slist, &schedulers->list) { - sch = container_of(slist, struct schedule_data, list); - if (task->type == sch->type) - return sch->ops->schedule_task_free(sch->data, task); - } + if (!task || !task->sch) + return -EINVAL; + + sch = task->sch; - return -ENODEV; + return sch->ops->schedule_task_free(sch->data, task); } +/* Only used in a stand-alone test and in a testbench test */ +#if CONFIG_SOF_BOOT_TEST_STANDALONE || CONFIG_LIBRARY /** See scheduler_ops::scheduler_free */ static inline void schedule_free(uint32_t flags) { @@ -360,23 +329,20 @@ static inline void schedule_free(uint32_t flags) sch->ops->scheduler_free(sch->data, flags); } } +#endif -/** See scheduler_ops::scheduler_restore */ -static inline int schedulers_restore(void) +/** See scheduler_ops::scheduler_init_context */ +static inline struct k_thread *scheduler_init_context(struct task *task) { - struct schedulers *schedulers = *arch_schedulers_get(); struct schedule_data *sch; - struct list_item *slist; - assert(schedulers); + assert(task && task->sch); + sch = task->sch; - list_for_item(slist, &schedulers->list) { - sch = container_of(slist, struct schedule_data, list); - if (sch->ops->scheduler_restore) - return sch->ops->scheduler_restore(sch->data); - } + if (sch->ops->scheduler_init_context) + return sch->ops->scheduler_init_context(sch->data, task); - return 0; + return NULL; } /** diff --git a/src/include/user/debug_stream_slot.h b/src/include/user/debug_stream_slot.h index 4464c9ed56b8..926b7f9634c8 100644 --- a/src/include/user/debug_stream_slot.h +++ b/src/include/user/debug_stream_slot.h @@ -127,6 +127,8 @@ struct debug_stream_record; * -ENODEV if debug stream slot is not configured * -ENOMEM if the record is too big */ -int debug_stream_slot_send_record(struct debug_stream_record *rec); +__syscall int debug_stream_slot_send_record(struct debug_stream_record *rec); + +#include #endif /* __SOC_DEBUG_WINDOW_SLOT_H__ */ diff --git a/src/include/user/mfcc.h b/src/include/user/mfcc.h index a2f3717daa52..286ee4f5e985 100644 --- a/src/include/user/mfcc.h +++ b/src/include/user/mfcc.h @@ -54,7 +54,9 @@ struct sof_mfcc_config { int16_t mel_scale; /**< Q4.12 default 1.0, use 0.25 for Whisper */ int16_t mmax_init; /**< Q8.7 default 0, with dynamic_mmax false, can sim. Whisper mmax */ int16_t mmax_coef; /**< Q1.15 decay coefficient for dynamic mmax, a small value for slow */ - uint32_t reserved[6]; + uint16_t dtx_trailing_silence_hops; /**< DTX: number of silence hops to send after speech, 0 = send first only */ + uint16_t dtx_silence_hops_interval; /**< DTX: send silence frame every Nth hop during VAD=0, 0 = disable */ + uint32_t reserved[5]; int32_t sample_frequency; /**< Hz. e.g. 16000 */ int32_t pmin; /**< Q1.31 linear power, limit minimum Mel energy, e.g. 1e-9 */ enum sof_mfcc_mel_log_type mel_log; /**< Use MEL_LOG_IS_LOG, LOG10 or DB*/ @@ -87,9 +89,10 @@ struct sof_mfcc_config { bool use_energy; /**< Must be false (0) */ bool dynamic_mmax; /**< Track max Mel value for clamp with top_db value */ bool enable_vad; /**< Run VAD algorithm */ - bool enable_dtx; /**< Reserved (stream once per second non-speech frames) */ + bool enable_dtx; /**< Discontinuous transmission: suppress silence after trailing frames */ bool update_controls; /**< Update controls with VAD decision */ - bool reserved_bool[5]; /* Reserved for future boolean flags, set to false (0) */ + bool compress_output; /**< Use compress PCM output: variable size, no zero padding */ + bool reserved_bool[4]; /* Reserved for future boolean flags, set to false (0) */ } __attribute__((packed)); #endif /* __USER_MFCC_H__ */ diff --git a/src/init/init.c b/src/init/init.c index e8d374c810ad..7976e2eb673e 100644 --- a/src/init/init.c +++ b/src/init/init.c @@ -41,7 +41,7 @@ #include #include #include -#include +#include #endif #include @@ -94,13 +94,15 @@ static bool check_restore(void) { struct idc *idc = *idc_get(); struct notify *notifier = *arch_notify_get(); - struct schedulers *schedulers = *arch_schedulers_get(); + struct schedulers **schedulers = arch_schedulers_get(); + struct schedulers **user_schedulers = arch_user_schedulers_get(); /* check whether basic core structures has been already allocated. If they * are available in memory, it means that this is not cold boot and memory * has not been powered off. */ - return !!idc && !!notifier && !!schedulers; + return !!idc && !!notifier && + ((schedulers && *schedulers) || (user_schedulers && *user_schedulers)); } static inline int secondary_core_restore(void) { return 0; }; @@ -110,6 +112,8 @@ __cold int secondary_core_init(struct sof *sof) int err; struct ll_schedule_domain *dma_domain; + assert_can_be_cold(); + /* check whether we are in a cold boot process or not (e.g. D0->D0ix * flow when primary core disables all secondary cores). If not, we do * not have allocate basic structures like e.g. schedulers, notifier, @@ -163,6 +167,8 @@ __cold int secondary_core_init(struct sof *sof) __cold static void print_version_banner(void) { + assert_can_be_cold(); + /* * Non-Zephyr builds emit the version banner in DMA-trace * init and this is done at a later time as otherwise the @@ -193,6 +199,8 @@ static log_timestamp_t default_get_timestamp(void) __cold static int primary_core_init(int argc, char *argv[], struct sof *sof) { + assert_can_be_cold(); + /* setup context */ sof->argc = argc; sof->argv = argv; diff --git a/src/ipc/ipc-common.c b/src/ipc/ipc-common.c index d0d248c9ec77..f0268b4c6c1c 100644 --- a/src/ipc/ipc-common.c +++ b/src/ipc/ipc-common.c @@ -330,11 +330,13 @@ __cold int ipc_init(struct sof *sof) #endif #ifdef __ZEPHYR__ - struct k_thread *thread = &sof->ipc->ipc_send_wq.thread; + k_tid_t thread; k_work_queue_start(&sof->ipc->ipc_send_wq, ipc_send_wq_stack, K_THREAD_STACK_SIZEOF(ipc_send_wq_stack), 1, NULL); + thread = k_work_queue_thread_get(&sof->ipc->ipc_send_wq); + k_thread_suspend(thread); #ifdef CONFIG_SCHED_CPU_MASK diff --git a/src/ipc/ipc-helper.c b/src/ipc/ipc-helper.c index ad7b3771a16b..1a2fcef4194e 100644 --- a/src/ipc/ipc-helper.c +++ b/src/ipc/ipc-helper.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -67,6 +68,7 @@ __cold struct comp_buffer *buffer_new(struct mod_alloc_ctx *alloc, return NULL; } +#if CONFIG_IPC_MAJOR_3 /* memory zones and caps are deprecated - convert to flags */ if (desc->caps & SOF_MEM_CAPS_DMA) flags |= SOF_MEM_FLAG_DMA; @@ -77,6 +79,7 @@ __cold struct comp_buffer *buffer_new(struct mod_alloc_ctx *alloc, if (desc->caps) tr_warn(&buffer_tr, "Deprecated buffer caps 0x%x used, convert to flags 0x%x", desc->caps, flags); +#endif /* allocate buffer */ buffer = buffer_alloc(alloc, desc->size, flags, PLATFORM_DCACHE_ALIGN, @@ -291,6 +294,7 @@ __cold int ipc_comp_free(struct ipc *ipc, uint32_t comp_id) struct ipc_comp_dev *icd; struct comp_buffer *buffer; struct comp_buffer *safe; + struct list_item *clist; uint32_t flags; assert_can_be_cold(); @@ -333,7 +337,15 @@ __cold int ipc_comp_free(struct ipc *ipc, uint32_t comp_id) return -EINVAL; } + /* Lock buffer lists to prevent racing with the LL scheduler. + * In user-space builds, use the LL scheduler's lock + * (re-entrant, so safe if caller already holds it). + */ +#ifdef CONFIG_SOF_USERSPACE_LL + user_ll_lock_sched(icd->core); +#else irq_local_disable(flags); +#endif comp_dev_for_each_producer_safe(icd->cd, buffer, safe) { comp_buffer_set_sink_component(buffer, NULL); /* This breaks the list, but we anyway delete all buffers */ @@ -346,7 +358,29 @@ __cold int ipc_comp_free(struct ipc *ipc, uint32_t comp_id) comp_buffer_reset_source_list(buffer); } +#ifdef CONFIG_SOF_USERSPACE_LL + user_ll_unlock_sched(icd->core); +#else irq_local_enable(flags); +#endif + + /* + * A completed pipeline stores raw comp_dev pointers in its + * source_comp/sink_comp/sched_comp fields. + */ + list_for_item(clist, &ipc->comp_list) { + struct ipc_comp_dev *ppl_icd = container_of(clist, struct ipc_comp_dev, list); + + if (ppl_icd->type != COMP_TYPE_PIPELINE) + continue; + + if (ppl_icd->pipeline->source_comp == icd->cd) + ppl_icd->pipeline->source_comp = NULL; + if (ppl_icd->pipeline->sink_comp == icd->cd) + ppl_icd->pipeline->sink_comp = NULL; + if (ppl_icd->pipeline->sched_comp == icd->cd) + ppl_icd->pipeline->sched_comp = NULL; + } /* free component and remove from list */ comp_free(icd->cd); diff --git a/src/ipc/ipc-zephyr.c b/src/ipc/ipc-zephyr.c index 66ada4ddaa05..b03c8420a8bb 100644 --- a/src/ipc/ipc-zephyr.c +++ b/src/ipc/ipc-zephyr.c @@ -9,7 +9,7 @@ // Andrey Borisovich // Adrian Warecki -#include +#include #include #include diff --git a/src/ipc/ipc3/dai.c b/src/ipc/ipc3/dai.c index ce7be827b887..af06e21955cd 100644 --- a/src/ipc/ipc3/dai.c +++ b/src/ipc/ipc3/dai.c @@ -95,6 +95,7 @@ int dai_config_dma_channel(struct dai_data *dd, struct comp_dev *dev, const void break; case SOF_DAI_AMD_HS: case SOF_DAI_AMD_HS_VIRTUAL: + case SOF_DAI_AMD_TDM: case SOF_DAI_AMD_SDW: { struct dai_config *params = (struct dai_config *)dd->dai->dev->config; @@ -184,21 +185,48 @@ int ipc_dai_data_config(struct dai_data *dd, struct comp_dev *dev) case SOF_DAI_IMX_ESAI: dd->config.burst_elems = dai_get_fifo_depth(dd->dai, dai->direction); break; - case SOF_DAI_AMD_BT: - dev->ipc_config.frame_fmt = SOF_IPC_FRAME_S16_LE; - break; - case SOF_DAI_AMD_SP: - case SOF_DAI_AMD_SP_VIRTUAL: - dev->ipc_config.frame_fmt = SOF_IPC_FRAME_S16_LE; - break; case SOF_DAI_AMD_DMIC: dev->ipc_config.frame_fmt = SOF_IPC_FRAME_S32_LE; if (dd->dma_buffer) audio_stream_set_frm_fmt(&dd->dma_buffer->stream, dev->ipc_config.frame_fmt); break; + case SOF_DAI_AMD_BT: + case SOF_DAI_AMD_SP: + case SOF_DAI_AMD_SP_VIRTUAL: case SOF_DAI_AMD_HS: case SOF_DAI_AMD_HS_VIRTUAL: + case SOF_DAI_AMD_TDM: +#if defined(CONFIG_AMD) && !defined(CONFIG_SOC_ACP_6_0) + { + struct acp_dma_dev_data *tdm_data = dd->dma->z_dev->data; + struct tdm_context *tdm_ctx; + + /* Allocate coherent memory for TDM context shared between + * IPC and DMA contexts. + */ + if (!tdm_data->dai_index_ptr) { + tdm_ctx = rzalloc(SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, + sizeof(*tdm_ctx)); + if (!tdm_ctx) + return -ENOMEM; + tdm_data->dai_index_ptr = tdm_ctx; + } else { + tdm_ctx = (struct tdm_context *)tdm_data->dai_index_ptr; + } + tdm_ctx->index = dd->dai->index; + tdm_ctx->frame_fmt = dev->ipc_config.frame_fmt; + /* AMD HW needs 24-bit data MSB-aligned in 32-bit word */ + if (dev->ipc_config.frame_fmt == SOF_IPC_FRAME_S24_4LE) { + dev->ipc_config.frame_fmt = SOF_IPC_FRAME_S24_4LE_MSB; + if (dd->dma_buffer) { + audio_stream_set_frm_fmt(&dd->dma_buffer->stream, + dev->ipc_config.frame_fmt); + } + } + } +#endif + break; case SOF_DAI_AMD_SDW: #if defined(CONFIG_AMD) && !defined(CONFIG_SOC_ACP_6_0) { diff --git a/src/ipc/ipc3/helper.c b/src/ipc/ipc3/helper.c index 4d87f042dd1d..e962a3670a86 100644 --- a/src/ipc/ipc3/helper.c +++ b/src/ipc/ipc3/helper.c @@ -81,7 +81,6 @@ static const struct comp_driver *get_drv(struct sof_ipc_comp *comp) struct comp_driver_info *info; struct sof_ipc_comp_ext *comp_ext; uintptr_t offset; - k_spinlock_key_t key; /* do we have extended data ? */ if (!comp->ext_data_length) { @@ -127,9 +126,10 @@ static const struct comp_driver *get_drv(struct sof_ipc_comp *comp) goto out; } - /* search driver list with UUID */ - key = k_spin_lock(&drivers->lock); - + /* + * search driver list with UUID; no locking needed as the driver + * list is only modified at boot and from the serialized IPC thread + */ list_for_item(clist, &drivers->list) { info = container_of(clist, struct comp_driver_info, list); @@ -148,8 +148,6 @@ static const struct comp_driver *get_drv(struct sof_ipc_comp *comp) *(uint32_t *)(&comp_ext->uuid[8]), *(uint32_t *)(&comp_ext->uuid[12])); - k_spin_unlock(&drivers->lock, key); - out: if (drv) tr_dbg(&comp_tr, "get_drv(), found driver type %d, uuid %pU", @@ -302,7 +300,11 @@ static int comp_specific_builder(struct sof_ipc_comp *comp, if (IPC_TAIL_IS_SIZE_INVALID(*proc)) return -EBADMSG; - if (proc->comp.hdr.size + proc->size > SOF_IPC_MSG_MAX_SIZE) + /* compare without adding the two host-supplied uint32_t values, + * which could wrap and let an oversized proc->size pass + */ + if (proc->comp.hdr.size > SOF_IPC_MSG_MAX_SIZE || + proc->size > SOF_IPC_MSG_MAX_SIZE - proc->comp.hdr.size) return -EBADMSG; config->process.type = proc->type; @@ -439,6 +441,8 @@ int ipc_pipeline_new(struct ipc *ipc, ipc_pipe_new *_pipe_desc) int ipc_pipeline_free(struct ipc *ipc, uint32_t comp_id) { struct ipc_comp_dev *ipc_pipe; + struct ipc_comp_dev *icd; + struct list_item *clist; int ret; /* check whether pipeline exists */ @@ -457,6 +461,19 @@ int ipc_pipeline_free(struct ipc *ipc, uint32_t comp_id) if (!cpu_is_me(ipc_pipe->core)) return ipc_process_on_core(ipc_pipe->core, false); + /* Clear stale cd->pipeline pointers on all components and buffers + * that still belong to this pipeline. A well-behaved host driver + * frees components before freeing the pipeline, but if it does not + * (or in fuzz/error paths) the dangling pointer would be a + * use-after-free on any subsequent IPC referencing that component. + */ + list_for_item(clist, &ipc->comp_list) { + icd = container_of(clist, struct ipc_comp_dev, list); + if (icd->type == COMP_TYPE_COMPONENT && + icd->cd && icd->cd->pipeline == ipc_pipe->pipeline) + icd->cd->pipeline = NULL; + } + /* free buffer and remove from list */ ret = pipeline_free(ipc_pipe->pipeline); if (ret < 0) { diff --git a/src/ipc/ipc3/host-page-table.c b/src/ipc/ipc3/host-page-table.c index e90203a79dce..dbd0fd0fe030 100644 --- a/src/ipc/ipc3/host-page-table.c +++ b/src/ipc/ipc3/host-page-table.c @@ -16,6 +16,25 @@ LOG_MODULE_DECLARE(ipc, CONFIG_SOF_LOG_LEVEL); +/* + * Size in bytes of the DSP-side buffer that receives the compressed host page + * table. DSP targets define PLATFORM_PAGE_TABLE_SIZE; the host/library builds + * (e.g. testbench, fuzzer) allocate it as HOST_PAGE_SIZE in drivers/host/ipc.c + * and do not define PLATFORM_PAGE_TABLE_SIZE, so fall back to that. + */ +#ifndef PLATFORM_PAGE_TABLE_SIZE +#define PLATFORM_PAGE_TABLE_SIZE HOST_PAGE_SIZE +#endif + +/* + * The compressed page table stores one 20-bit entry per host page, so the + * number of pages whose entries fit in the page table buffer is + * (buffer bytes * 8 bits/byte) / 20 bits/page. + */ +#define HOST_PAGE_TABLE_BITS_PER_PAGE 20 +#define HOST_PAGE_TABLE_MAX_PAGES \ + (PLATFORM_PAGE_TABLE_SIZE * 8 / HOST_PAGE_TABLE_BITS_PER_PAGE) + /* * Parse the host page tables and create the audio DMA SG configuration * for host audio DMA buffer. This involves creating a dma_sg_elem for each @@ -216,6 +235,19 @@ int ipc_process_host_buffer(struct ipc *ipc, struct ipc_data_host_buffer *data_host_buffer; int err; + /* + * The host-supplied page count is used both to size DSP-side + * allocations and to compute the DMA transfer length for the + * compressed page table. Reject a count that would not fit in the + * page table buffer before doing any arithmetic that could overflow + * (ring->pages * 20). pages == 0 is also invalid and would underflow + * the ring->size sanity check in ipc_parse_page_descriptors(). + */ + if (ring->pages == 0 || ring->pages > HOST_PAGE_TABLE_MAX_PAGES) { + tr_err(&ipc_tr, "ipc: invalid page count %u", ring->pages); + return -EINVAL; + } + data_host_buffer = ipc_platform_get_host_buffer(ipc); dma_sg_init(elem_array); diff --git a/src/ipc/ipc4/dai.c b/src/ipc/ipc4/dai.c index 58c08d9ec04b..1540d95cf0f0 100644 --- a/src/ipc/ipc4/dai.c +++ b/src/ipc/ipc4/dai.c @@ -236,9 +236,9 @@ void dai_dma_release(struct dai_data *dd, struct comp_dev *dev) * TODO: refine power management when stream is paused */ /* if reset is after pause dma has already been stopped */ - dma_stop(dd->dma->z_dev, dd->chan_index); + sof_dma_stop(dd->dma, dd->chan_index); - dma_release_channel(dd->dma->z_dev, dd->chan_index); + sof_dma_release_channel(dd->dma, dd->chan_index); dd->chan_index = -EINVAL; } } @@ -450,7 +450,7 @@ void dai_dma_position_update(struct dai_data *dd, struct comp_dev *dev) if (!dd->slot_info.node_id) return; - ret = dma_get_status(dd->dma->z_dev, dd->chan_index, &status); + ret = sof_dma_get_status(dd->dma, dd->chan_index, &status); if (ret < 0) return; diff --git a/src/ipc/ipc4/handler-user.c b/src/ipc/ipc4/handler-user.c index df243fa6ff58..48131f2f6aae 100644 --- a/src/ipc/ipc4/handler-user.c +++ b/src/ipc/ipc4/handler-user.c @@ -149,6 +149,11 @@ static struct ipc_comp_dev *pipeline_get_host_dev(struct ipc_comp_dev *ppl_icd) struct ipc *ipc = ipc_get(); int host_id; + if (!ppl_icd->pipeline->source_comp || !ppl_icd->pipeline->sink_comp) { + ipc_cmd_err(&ipc_tr, "pipeline %d: source/sink comp freed", ppl_icd->id); + return NULL; + } + /* If the source component's direction is not set but the sink's direction is, * this block will copy the direction from the sink to the source component and * mark the source's direction as set. @@ -601,6 +606,8 @@ __cold static int ipc4_process_chain_dma(struct ipc4_message_request *ipc4) ret = ipc4_chain_dma_state(cdma_comp->cd, &cdma); if (ret < 0) { comp_free(cdma_comp->cd); + list_item_del(&cdma_comp->list); + rfree(cdma_comp); return IPC4_FAILURE; } @@ -611,9 +618,6 @@ __cold static int ipc4_process_chain_dma(struct ipc4_message_request *ipc4) if (ret < 0) return IPC4_INVALID_CHAIN_STATE_TRANSITION; - if (!cdma.primary.r.allocate && !cdma.primary.r.enable) - list_item_del(&cdma_comp->list); - return IPC4_SUCCESS; #else return IPC4_UNAVAILABLE; @@ -1040,6 +1044,14 @@ __cold static int ipc4_get_large_config_module_instance(struct ipc4_message_requ /* check for vendor param first */ if (config.extension.r.large_param_id == VENDOR_CONFIG_PARAM) { + /* data_off_size is a 20-bit host-controlled field, so it can + * claim far more than the hostbox can physically hold. + */ + if (data_offset > MAILBOX_HOSTBOX_SIZE) { + ipc_cmd_err(&ipc_tr, "data_off_size %u exceeds mailbox bound", + data_offset); + return IPC4_INVALID_CONFIG_DATA_STRUCT; + } /* For now only vendor_config case uses payload from hostbox */ dcache_invalidate_region((__sparse_force void __sparse_cache *)MAILBOX_HOSTBOX_BASE, config.extension.r.data_off_size); @@ -1101,15 +1113,21 @@ __cold static int ipc4_set_vendor_config_module_instance(struct comp_dev *dev, assert_can_be_cold(); + /* Validate host-controlled payload size before any use or arithmetic. */ + if (data_off_size > MAILBOX_HOSTBOX_SIZE) { + tr_err(&ipc_tr, "data_off_size greater than mailbox %u > %u", + data_off_size, (uint32_t)MAILBOX_HOSTBOX_SIZE); + return IPC4_INVALID_CONFIG_DATA_STRUCT; + } + if (init_block && data_off_size < sizeof(struct sof_tlv)) { + tr_err(&ipc_tr, "init_block data_off_size too small %u < %zu", + data_off_size, sizeof(struct sof_tlv)); + return IPC4_INVALID_CONFIG_DATA_STRUCT; + } + /* Old FW comment: bursted configs */ if (init_block && final_block) { const struct sof_tlv *tlv = (struct sof_tlv *)data; - /* if there is no payload in this large config set - * (4 bytes type | 4 bytes length=0 | no value) - * we do not handle such case - */ - if (data_off_size < sizeof(struct sof_tlv) || data_off_size > MAILBOX_HOSTBOX_SIZE) - return IPC4_INVALID_CONFIG_DATA_STRUCT; /* ===Iterate over payload=== * Payload can have multiple sof_tlv structures inside, diff --git a/src/ipc/ipc4/helper.c b/src/ipc/ipc4/helper.c index 8e3073ab7797..d5c09c3f7bf7 100644 --- a/src/ipc/ipc4/helper.c +++ b/src/ipc/ipc4/helper.c @@ -145,6 +145,20 @@ __cold struct comp_dev *comp_new_ipc4(struct ipc4_module_init_instance *module_i ipc_config.ipc_config_size); return NULL; } + + /* Reject a module naming a non-existent parent pipeline: otherwise + * dev->pipeline stays NULL and a later init path (e.g. the copier) + * dereferences it. IPC4_INVALID_PIPELINE_ID is exempt - it marks base FW + * modules with no parent pipeline. IPC_COMP_ALL: the pipeline may run on a + * different core than this module. + */ + if (ipc_config.pipeline_id != IPC4_INVALID_PIPELINE_ID && + !ipc_get_comp_by_ppl_id(ipc_get(), COMP_TYPE_PIPELINE, ipc_config.pipeline_id, + IPC_COMP_ALL)) { + tr_err(&ipc_tr, "comp 0x%x: parent pipeline %u does not exist", comp_id, + (uint32_t)ipc_config.pipeline_id); + return NULL; + } #ifdef CONFIG_DCACHE_LINE_SIZE if (!IS_ENABLED(CONFIG_LIBRARY)) sys_cache_data_invd_range((__sparse_force void __sparse_cache *) @@ -317,10 +331,9 @@ __cold static int ipc4_create_pipeline_payload_decode(char *data, } pparams->mem_data = mem_data; tr_info(&ipc_tr, - "init_ext_obj_mem_data domain %u stack %u interim %u lifetime %u shared %u", + "init_ext_obj_mem_data domain %u stack %u heap %u", mem_data->domain_id, mem_data->stack_bytes, - mem_data->interim_heap_bytes, mem_data->lifetime_heap_bytes, - mem_data->shared_bytes); + mem_data->heap_bytes); break; } default: @@ -449,10 +462,21 @@ __cold static int ipc_pipeline_module_free(uint32_t pipeline_id) struct ipc *ipc = ipc_get(); struct ipc_comp_dev *icd; int ret; +#ifdef CONFIG_SOF_USERSPACE_LL + int ppl_core; +#endif assert_can_be_cold(); icd = ipc_get_comp_by_ppl_id(ipc, COMP_TYPE_COMPONENT, pipeline_id, IPC_COMP_ALL); + if (!icd) + return IPC4_SUCCESS; + +#ifdef CONFIG_SOF_USERSPACE_LL + ppl_core = icd->core; + user_ll_lock_sched(ppl_core); +#endif + while (icd) { struct comp_buffer *buffer; struct comp_buffer *safe; @@ -482,12 +506,20 @@ __cold static int ipc_pipeline_module_free(uint32_t pipeline_id) else ret = ipc_comp_free(ipc, icd->id); - if (ret) + if (ret) { +#ifdef CONFIG_SOF_USERSPACE_LL + user_ll_unlock_sched(ppl_core); +#endif return IPC4_INVALID_RESOURCE_STATE; + } icd = ipc_get_comp_by_ppl_id(ipc, COMP_TYPE_COMPONENT, pipeline_id, IPC_COMP_ALL); } +#ifdef CONFIG_SOF_USERSPACE_LL + user_ll_unlock_sched(ppl_core); +#endif + return IPC4_SUCCESS; } @@ -561,21 +593,25 @@ __cold static struct comp_buffer *ipc4_create_buffer(struct comp_dev *src, bool * disable any interrupts. */ -#define ll_block(cross_core_bind, flags) \ +#if CONFIG_SOF_USERSPACE_LL +#error "CONFIG_SOF_USERSPACE_LL not compatible with cross-core streams" +#else +#define ll_block(src_core, dst_core, flags) \ do { \ - if (cross_core_bind) \ + if (src_core != dst_core) \ domain_block(sof_get()->platform_timer_domain); \ else \ irq_local_disable(flags); \ } while (0) -#define ll_unblock(cross_core_bind, flags) \ +#define ll_unblock(src_core, dst_core, flags) \ do { \ - if (cross_core_bind) \ + if (src_core != dst_core) \ domain_unblock(sof_get()->platform_timer_domain); \ else \ irq_local_enable(flags); \ } while (0) +#endif /* Calling both ll_block() and ll_wait_finished_on_core() makes sure LL will not start its * next cycle and its current cycle on specified core has finished. @@ -607,8 +643,22 @@ static int ll_wait_finished_on_core(struct comp_dev *dev) #else -#define ll_block(cross_core_bind, flags) irq_local_disable(flags) -#define ll_unblock(cross_core_bind, flags) irq_local_enable(flags) +#if CONFIG_SOF_USERSPACE_LL +/* note: cross-core streams are disabled so src_core==dst_core */ +#define ll_block(src_core, dst_core, flags) \ + do { \ + user_ll_lock_sched(src_core); \ + ARG_UNUSED(flags); \ + } while(0) +#define ll_unblock(src_core, dst_core, flags) \ + do { \ + user_ll_unlock_sched(src_core); \ + ARG_UNUSED(flags); \ + } while(0) +#else +#define ll_block(src_core, dst_core, flags) irq_local_disable(flags) +#define ll_unblock(src_core, dst_core, flags) irq_local_enable(flags) +#endif #endif @@ -795,7 +845,7 @@ __cold int ipc_comp_connect(struct ipc *ipc, ipc_pipe_comp_connect *_connect) * blocked on corresponding core(s) to prevent IPC or IDC task getting preempted which * could result in buffers being only half connected when a pipeline task gets executed. */ - ll_block(cross_core_bind, flags); + ll_block(source->ipc_config.core, sink->ipc_config.core, flags); if (cross_core_bind) { #if CONFIG_CROSS_CORE_STREAM @@ -852,7 +902,7 @@ __cold int ipc_comp_connect(struct ipc *ipc, ipc_pipe_comp_connect *_connect) source->direction_set = true; } - ll_unblock(cross_core_bind, flags); + ll_unblock(source->ipc_config.core, sink->ipc_config.core, flags); return IPC4_SUCCESS; @@ -866,7 +916,7 @@ __cold int ipc_comp_connect(struct ipc *ipc, ipc_pipe_comp_connect *_connect) e_sink_connect: pipeline_disconnect(source, buffer, PPL_CONN_DIR_COMP_TO_BUFFER); free: - ll_unblock(cross_core_bind, flags); + ll_unblock(source->ipc_config.core, sink->ipc_config.core, flags); buffer_free(buffer); return IPC4_INVALID_RESOURCE_STATE; } @@ -921,30 +971,42 @@ __cold int ipc_comp_disconnect(struct ipc *ipc, ipc_pipe_comp_connect *_connect) if (!buffer) return IPC4_INVALID_RESOURCE_ID; + /* + * The buffer was located on the source's consumer list, but the sink was + * resolved solely from the host-supplied dst_module_id/dst_instance_id. + * Make sure the buffer is actually connected to that sink, otherwise an + * incorrect dst would leave the real sink bound to a buffer we are about + * to free, while unbinding an unrelated component instead. + */ + if (comp_buffer_get_sink_component(buffer) != sink) { + tr_err(&ipc_tr, "buffer %#x is not connected to sink %#x", buffer_id, sink_id); + return IPC4_INVALID_RESOURCE_ID; + } + /* * Disconnect and unbind buffer from source/sink components and continue to free the buffer * even in case of errors. Block LL processing during disconnect and unbinding to prevent * IPC or IDC task getting preempted which could result in buffers being only half connected * when a pipeline task gets executed. */ - ll_block(cross_core_unbind, flags); + ll_block(src->ipc_config.core, sink->ipc_config.core, flags); if (cross_core_unbind) { #if CONFIG_CROSS_CORE_STREAM /* Make sure LL has finished on both cores */ if (!cpu_is_me(src->ipc_config.core)) if (ll_wait_finished_on_core(src) < 0) { - ll_unblock(cross_core_unbind, flags); + ll_unblock(src->ipc_config.core, sink->ipc_config.core, flags); return IPC4_FAILURE; } if (!cpu_is_me(sink->ipc_config.core)) if (ll_wait_finished_on_core(sink) < 0) { - ll_unblock(cross_core_unbind, flags); + ll_unblock(src->ipc_config.core, sink->ipc_config.core, flags); return IPC4_FAILURE; } #else tr_err(&ipc_tr, "Cross-core binding is disabled"); - ll_unblock(cross_core_unbind, flags); + ll_unblock(src->ipc_config.core, sink->ipc_config.core, flags); return IPC4_FAILURE; #endif } @@ -961,7 +1023,7 @@ __cold int ipc_comp_disconnect(struct ipc *ipc, ipc_pipe_comp_connect *_connect) unbind_data.source = audio_buffer_get_source(&buffer->audio_buffer); ret1 = comp_unbind(sink, &unbind_data); - ll_unblock(cross_core_unbind, flags); + ll_unblock(src->ipc_config.core, sink->ipc_config.core, flags); buffer_free(buffer); @@ -1131,12 +1193,9 @@ __cold static const struct comp_driver *ipc4_search_for_drv(const void *uuid) struct list_item *clist; const struct comp_driver *drv = NULL; struct comp_driver_info *info; - uint32_t flags; assert_can_be_cold(); - irq_local_disable(flags); - /* search driver list with UUID */ list_for_item(clist, &drivers->list) { info = container_of(clist, struct comp_driver_info, @@ -1151,7 +1210,6 @@ __cold static const struct comp_driver *ipc4_search_for_drv(const void *uuid) } } - irq_local_enable(flags); return drv; } @@ -1300,25 +1358,82 @@ 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_all_dma_configs_tlvs_only(struct ipc_config_dai *dai, + uint8_t *data_buffer, size_t size) +{ + uintptr_t end_addr = (uintptr_t)data_buffer + size; + struct sof_tlv *tlvs; + struct ipc_dma_config *dma_cfg; + int count = 0; + + for (tlvs = (struct sof_tlv *)data_buffer; tlvs && (uintptr_t)tlvs < end_addr; + tlvs = tlv_next(tlvs)) { + if ((uintptr_t)tlvs->value + tlvs->length > end_addr) { + tr_err(&ipc_tr, "Unexpected TLV length %d: exceeds buffer size", tlvs->length); + return IPC4_INVALID_REQUEST; + } + + 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) { - uint32_t end_addr = (uint32_t)data_buffer + size; + uintptr_t end_addr = (uintptr_t)data_buffer + size; struct ipc_dma_config *dma_cfg; struct sof_tlv *tlvs; - for (tlvs = (struct sof_tlv *)data_buffer; tlvs && (uint32_t)tlvs < end_addr; + for (tlvs = (struct sof_tlv *)data_buffer; tlvs && (uintptr_t)tlvs < end_addr; tlvs = tlv_next(tlvs)) { + /* Reject a host TLV that overruns the buffer or wraps tlv_next(). */ + uintptr_t remaining = end_addr - (uintptr_t)tlvs; + + if (remaining < sizeof(*tlvs) || tlvs->length > remaining - sizeof(*tlvs)) + return IPC4_INVALID_REQUEST; + dma_cfg = tlv_value_ptr_get(tlvs, GTW_DMA_CONFIG_ID); if (!dma_cfg) continue; + uint32_t device_count = dma_cfg->channel_map.device_count; + + if (device_count > GTW_DMA_DEVICE_MAX_COUNT) { + tr_err(&ipc_tr, "device_count %u exceeds max %u", + device_count, GTW_DMA_DEVICE_MAX_COUNT); + return IPC4_INVALID_REQUEST; + } + /* To be able to retrieve proper DMA config we need to check if * device_id value (which is alh_id) is equal to device_address. * They both contain SNDW master id and PDI. If they match then * proper config is found. */ - for (uint32_t i = 0; i < dma_cfg->channel_map.device_count; i++) { + for (uint32_t i = 0; i < device_count; i++) { if (dma_cfg->channel_map.map[i].device_address == device_id) { dai->host_dma_config[dma_cfg_idx] = dma_cfg; return IPC4_SUCCESS; diff --git a/src/lib/agent.c b/src/lib/agent.c index 1030e88dd7f2..5679fbddfe59 100644 --- a/src/lib/agent.c +++ b/src/lib/agent.c @@ -93,10 +93,12 @@ static enum task_state validate(void *data) return SOF_TASK_STATE_RESCHEDULE; } -void sa_init(struct sof *sof, uint64_t timeout) +__cold void sa_init(struct sof *sof, uint64_t timeout) { uint64_t ticks; + assert_can_be_cold(); + if (timeout > UINT_MAX) tr_warn(&sa_tr, "timeout > %u", UINT_MAX); else diff --git a/src/lib/ams.c b/src/lib/ams.c index 31bca13ce833..7f4e6ebb8664 100644 --- a/src/lib/ams.c +++ b/src/lib/ams.c @@ -16,7 +16,6 @@ #include #include #include -#include #include #include #include @@ -285,9 +284,13 @@ static uint32_t ams_push_slot(struct ams_shared_context __sparse_cache *ctx_shar for (uint32_t i = 0; i < ARRAY_SIZE(ctx_shared->slots); ++i) { if (ctx_shared->slot_uses[i] == 0) { + /* the slot only carries the payload struct (read back + * via u.msg); message points to a caller-owned buffer + * rather than inline data, so copy exactly the struct + */ err = memcpy_s((__sparse_force void *)ctx_shared->slots[i].u.msg_raw, sizeof(ctx_shared->slots[i].u.msg_raw), - msg, AMS_MESSAGE_SIZE(msg)); + msg, sizeof(*msg)); if (err != 0) return AMS_INVALID_SLOT; @@ -578,13 +581,15 @@ static int ams_create_shared_context(struct ams_shared_context *ctx) return 0; } -int ams_init(void) +__cold int ams_init(void) { struct ams_shared_context *ams_shared_ctx; struct async_message_service **ams = arch_ams_get(); struct sof *sof; int ret = 0; + assert_can_be_cold(); + *ams = rzalloc(SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, sizeof(**ams)); if (!*ams) diff --git a/src/lib/clk.c b/src/lib/clk.c index d93ab81c680b..1b5025b616cb 100644 --- a/src/lib/clk.c +++ b/src/lib/clk.c @@ -89,7 +89,6 @@ uint64_t clock_ticks_per_sample(int clock, uint32_t sample_rate) uint32_t ticks_per_msec; uint64_t ticks_per_sample; - platform_shared_get(clk_info, sizeof(*clk_info)); ticks_per_msec = clk_info->freqs[clk_info->current_freq_idx].ticks_per_msec; ticks_per_sample = sample_rate ? ticks_per_msec * 1000 / sample_rate : 0; diff --git a/src/lib/cpu-clk-manager.c b/src/lib/cpu-clk-manager.c index 59744063e4b9..df917fab4f9d 100644 --- a/src/lib/cpu-clk-manager.c +++ b/src/lib/cpu-clk-manager.c @@ -28,6 +28,13 @@ static int request_freq_change(unsigned int core, int freq) break; } + /* if no entry was larger than the requested frequency the loop ends with + * selected_freq_id == clk->freqs_num; clamp to the last valid entry + * before indexing + */ + if (selected_freq_id == clk->freqs_num) + selected_freq_id = clk->freqs_num - 1; + /* don't change clock frequency if already using proper clock */ current_freq = clock_get_freq(core); if (clk->freqs[selected_freq_id].freq != current_freq) diff --git a/src/lib/dai.c b/src/lib/dai.c index 7a8e44087a65..68163282adc1 100644 --- a/src/lib/dai.c +++ b/src/lib/dai.c @@ -189,6 +189,9 @@ const struct device *zephyr_dev[] = { #if CONFIG_DAI_AMD_SDW DT_FOREACH_STATUS_OKAY(amd_acp_sdw_dai, GET_DEVICE_LIST) #endif +#if CONFIG_DAI_AMD_TDM + DT_FOREACH_STATUS_OKAY(amd_tdm_dai, GET_DEVICE_LIST) +#endif #if CONFIG_DAI_INTEL_UAOL DT_FOREACH_STATUS_OKAY(intel_uaol_dai, GET_DEVICE_LIST) #endif @@ -223,27 +226,27 @@ static int sof_dai_type_to_zephyr(uint32_t type) return DAI_IMX_SAI; case SOF_DAI_IMX_ESAI: return DAI_IMX_ESAI; - case SOF_DAI_AMD_BT: - return DAI_AMD_BT; - case SOF_DAI_AMD_SP: - return DAI_AMD_SP; case SOF_DAI_AMD_DMIC: return DAI_AMD_DMIC; case SOF_DAI_MEDIATEK_AFE: return DAI_MEDIATEK_AFE; case SOF_DAI_IMX_MICFIL: return DAI_IMX_MICFIL; - case SOF_DAI_AMD_HS: - case SOF_DAI_AMD_SP_VIRTUAL: - case SOF_DAI_AMD_HS_VIRTUAL: case SOF_DAI_AMD_SDW: return DAI_AMD_SDW; + case SOF_DAI_AMD_HS: + case SOF_DAI_AMD_HS_VIRTUAL: + case SOF_DAI_AMD_SP: + case SOF_DAI_AMD_SP_VIRTUAL: + case SOF_DAI_AMD_BT: + case SOF_DAI_AMD_TDM: + return DAI_AMD_TDM; default: return -EINVAL; } } -const struct device *dai_get_device(enum sof_ipc_dai_type type, uint32_t index) +const struct device *z_impl_dai_get_device(enum sof_ipc_dai_type type, uint32_t index) { struct dai_config cfg; int z_type; @@ -305,6 +308,19 @@ static void dai_set_device_params(struct dai *d) d->dma_dev = SOF_DMA_DEV_SW; d->dma_caps = SOF_DMA_CAP_SW; break; + /* All TDM-capable AMD DAIs share acp_tdm_dma. */ + case SOF_DAI_AMD_HS: + case SOF_DAI_AMD_HS_VIRTUAL: + case SOF_DAI_AMD_SP: + case SOF_DAI_AMD_SP_VIRTUAL: + case SOF_DAI_AMD_BT: + d->dma_dev = SOF_DMA_DEV_HS | SOF_DMA_DEV_SP | SOF_DMA_DEV_BT; + d->dma_caps = SOF_DMA_CAP_HS | SOF_DMA_CAP_SP | SOF_DMA_CAP_BT; + break; + case SOF_DAI_AMD_TDM: + d->dma_dev = SOF_DMA_DEV_TDM; + d->dma_caps = SOF_DMA_CAP_TDM; + break; case SOF_DAI_MEDIATEK_AFE: d->dma_dev = SOF_DMA_DEV_AFE_MEMIF; break; diff --git a/src/lib/dma.c b/src/lib/dma.c index 70ac5c975058..137dfb4e0fea 100644 --- a/src/lib/dma.c +++ b/src/lib/dma.c @@ -5,6 +5,7 @@ // Author: Ranjani Sridharan #include +#include #include #include #include @@ -134,7 +135,7 @@ void z_impl_sof_dma_put(struct sof_dma *dma) key = k_spin_lock(&dma->lock); if (--dma->sref == 0) { - rfree(dma->chan); + sof_heap_free(dma->heap, dma->chan); dma->chan = NULL; } @@ -146,18 +147,20 @@ void z_impl_sof_dma_put(struct sof_dma *dma) static int dma_init(struct sof_dma *dma) { struct dma_chan_data *chan; + struct k_heap *heap = sof_sys_user_heap_get(); int i; /* allocate dma channels */ - dma->chan = rzalloc(SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, - sizeof(struct dma_chan_data) * dma->plat_data.channels); - + dma->chan = sof_heap_alloc(heap, SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, + sizeof(struct dma_chan_data) * dma->plat_data.channels, 0); if (!dma->chan) { - tr_err(&dma_tr, "dma_probe_sof(): dma %d allocaction of channels failed", + tr_err(&dma_tr, "dma %d allocation of channels failed", dma->plat_data.id); return -ENOMEM; } + dma->heap = heap; + memset(dma->chan, 0, sizeof(struct dma_chan_data) * dma->plat_data.channels); /* init work */ for (i = 0, chan = dma->chan; i < dma->plat_data.channels; i++, chan++) { diff --git a/src/lib/notifier.c b/src/lib/notifier.c index e398417a6fd1..102dc55fc7b3 100644 --- a/src/lib/notifier.c +++ b/src/lib/notifier.c @@ -190,10 +190,13 @@ void notifier_event(const void *caller, enum notify_id type, uint32_t core_mask, } } -void init_system_notify(struct sof *sof) +__cold void init_system_notify(struct sof *sof) { struct notify **notify = arch_notify_get(); int i; + + assert_can_be_cold(); + *notify = rzalloc(SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, sizeof(**notify)); if (!*notify) { @@ -206,8 +209,7 @@ void init_system_notify(struct sof *sof) list_init(&(*notify)->list[i]); if (cpu_get_id() == PLATFORM_PRIMARY_CORE_ID) - sof->notify_data = platform_shared_get(notify_data_shared, - sizeof(notify_data_shared)); + sof->notify_data = notify_data_shared; } void free_system_notify(void) diff --git a/src/lib/objpool.c b/src/lib/objpool.c index 6925e6f7070a..9636e018d3d1 100644 --- a/src/lib/objpool.c +++ b/src/lib/objpool.c @@ -44,10 +44,10 @@ static int objpool_add(struct objpool_head *head, unsigned int n, size_t size, u pobjpool = sof_heap_alloc(head->heap, flags, aligned_size + sizeof(*pobjpool), 0); else if (flags & SOF_MEM_FLAG_COHERENT) - pobjpool = vregion_alloc_coherent(head->vreg, VREGION_MEM_TYPE_INTERIM, + pobjpool = vregion_alloc_coherent(head->vreg, aligned_size + sizeof(*pobjpool)); else - pobjpool = vregion_alloc(head->vreg, VREGION_MEM_TYPE_INTERIM, + pobjpool = vregion_alloc(head->vreg, aligned_size + sizeof(*pobjpool)); if (!pobjpool) diff --git a/src/library_manager/lib_manager.c b/src/library_manager/lib_manager.c index e090a321a056..19c317ed1089 100644 --- a/src/library_manager/lib_manager.c +++ b/src/library_manager/lib_manager.c @@ -37,6 +37,7 @@ #if CONFIG_LLEXT #include #endif +#include #if CONFIG_LIBRARY_AUTH_SUPPORT #include @@ -110,16 +111,17 @@ static int lib_manager_auth_proc(const void *buffer_data, size_t buffer_size, while (auth_api_busy(auth_ctx)) ; + if (phase != AUTH_PHASE_LAST) + return 0; + ret = auth_api_result(auth_ctx); + auth_api_cleanup(auth_ctx); if (ret != AUTH_IMAGE_TRUSTED) { tr_err(&lib_manager_tr, "Untrusted library!"); return -EACCES; } - if (phase == AUTH_PHASE_LAST) - auth_api_cleanup(auth_ctx); - return 0; } #endif /* CONFIG_LIBRARY_AUTH_SUPPORT */ @@ -128,7 +130,7 @@ static int lib_manager_auth_proc(const void *buffer_data, size_t buffer_size, #define PAGE_SZ CONFIG_MM_DRV_PAGE_SIZE -static int lib_manager_load_data_from_storage(void __sparse_cache *vma, void *s_addr, uint32_t size, +static int lib_manager_load_data_from_storage(void __sparse_cache *vma, void *s_addr, size_t size, uint32_t flags) { /* Region must be first mapped as writable in order to initialize its contents. */ @@ -146,16 +148,26 @@ static int lib_manager_load_data_from_storage(void __sparse_cache *vma, void *s_ return sys_mm_drv_update_region_flags((__sparse_force void *)vma, size, flags); } -static int lib_manager_load_module(const uint32_t module_id, const struct sof_man_module *const mod) +static int lib_manager_load_module(const struct sof_man_fw_desc *const desc, + const uint32_t module_id, const struct sof_man_module *const mod) { struct lib_manager_mod_ctx *ctx = lib_manager_get_mod_ctx(module_id); const uintptr_t load_offset = POINTER_TO_UINT(ctx->base_addr); + size_t lib_size; + size_t file_offset; void *src; void __sparse_cache *va_base; size_t size; uint32_t flags; int ret, idx; + /* + * ELF file segment offsets and sizes come from library files, so they + * have to be validated. + */ + if (size_mul_overflow(desc->header.preload_page_count, PAGE_SZ, &lib_size)) + return -EOVERFLOW; + for (idx = 0; idx < ARRAY_SIZE(mod->segment); ++idx) { if (!mod->segment[idx].flags.r.load) continue; @@ -167,9 +179,23 @@ static int lib_manager_load_module(const uint32_t module_id, const struct sof_ma else if (!mod->segment[idx].flags.r.readonly) flags = SYS_MM_MEM_PERM_RW; - src = UINT_TO_POINTER(mod->segment[idx].file_offset + load_offset); + file_offset = mod->segment[idx].file_offset; + if (size_mul_overflow(mod->segment[idx].flags.r.length, PAGE_SZ, &size)) { + ret = -EOVERFLOW; + goto err; + } + + /* Reject segments that would read outside the loaded library image. */ + if (file_offset > lib_size || size > lib_size - file_offset) { + tr_err(&lib_manager_tr, + "segment %d out of bounds: file_offset %#zx, size %#zx, total %#zx", + idx, file_offset, size, lib_size); + ret = -ENOSPC; + goto err; + } + + src = UINT_TO_POINTER(file_offset + load_offset); va_base = (void __sparse_cache *)UINT_TO_POINTER(mod->segment[idx].v_base_addr); - size = mod->segment[idx].flags.r.length * PAGE_SZ; ret = lib_manager_load_data_from_storage(va_base, src, size, flags); if (ret < 0) goto err; @@ -229,7 +255,8 @@ static int lib_manager_load_libcode_modules(const uint32_t module_id) for (idx = 0; idx < desc->header.num_module_entries; ++idx, ++module_entry) { if (module_entry->type.lib_code) { - ret = lib_manager_load_module(lib_id << LIB_MANAGER_LIB_ID_SHIFT | idx, + ret = lib_manager_load_module(desc, + lib_id << LIB_MANAGER_LIB_ID_SHIFT | idx, module_entry); if (ret < 0) goto err; @@ -280,7 +307,10 @@ void lib_manager_get_instance_bss_address(uint32_t instance_id, const struct sof_man_module *mod, void __sparse_cache **va_addr, size_t *size) { - *size = mod->segment[SOF_MAN_SEGMENT_BSS].flags.r.length / mod->instance_max_count * + /* mod->instance_max_count should never be 0, assume 1 if that happens */ + unsigned int inst_cnt = mod->instance_max_count ? : 1; + + *size = mod->segment[SOF_MAN_SEGMENT_BSS].flags.r.length / inst_cnt * PAGE_SZ; size_t inst_offset = *size * instance_id; *va_addr = (void __sparse_cache *)(mod->segment[SOF_MAN_SEGMENT_BSS].v_base_addr + @@ -337,7 +367,8 @@ static int lib_manager_free_module_instance(uint32_t instance_id, const struct s * * Function is responsible to allocate module in available free memory and assigning proper address. */ -static uintptr_t lib_manager_allocate_module(const struct sof_man_module *mod, +static uintptr_t lib_manager_allocate_module(const struct sof_man_fw_desc *const desc, + const struct sof_man_module *mod, const struct comp_ipc_config *ipc_config, const void *ipc_specific_config) { @@ -350,7 +381,7 @@ static uintptr_t lib_manager_allocate_module(const struct sof_man_module *mod, if (module_is_llext(mod)) return llext_manager_allocate_module(ipc_config, ipc_specific_config); - ret = lib_manager_load_module(module_id, mod); + ret = lib_manager_load_module(desc, module_id, mod); if (ret < 0) return 0; @@ -423,7 +454,8 @@ static int lib_manager_free_module(const uint32_t component_id) #define PAGE_SZ 4096 /* equals to MAN_PAGE_SIZE used by rimage */ -static uintptr_t lib_manager_allocate_module(const struct comp_ipc_config *ipc_config, +static uintptr_t lib_manager_allocate_module(const struct sof_man_fw_desc *const desc, + const struct comp_ipc_config *ipc_config, const void *ipc_specific_config, const void **buildinfo) { tr_err(&lib_manager_tr, "Dynamic module allocation is not supported"); @@ -567,6 +599,8 @@ static enum buildinfo_mod_type lib_manager_get_module_type(const struct sof_man_ const struct sof_module_api_build_info *const build_info = (const struct sof_module_api_build_info *)((const char *)desc - SOF_MAN_ELF_TEXT_OFFSET + mod->segment[SOF_MAN_SEGMENT_TEXT].file_offset); + const size_t lib_size = (size_t)desc->header.preload_page_count * PAGE_SZ; + const uint32_t text_off = mod->segment[SOF_MAN_SEGMENT_TEXT].file_offset; /* * llext modules store build info structure in separate section which is not accessible now. @@ -574,6 +608,17 @@ static enum buildinfo_mod_type lib_manager_get_module_type(const struct sof_man_ if (module_is_llext(mod)) return MOD_TYPE_LLEXT; + /* + * build_info is derived from a manifest-supplied file_offset; bound it + * against the library image size before dereferencing so a crafted + * offset cannot read outside the library buffer. + */ + if (text_off > lib_size || lib_size - text_off < sizeof(*build_info)) { + tr_err(&lib_manager_tr, "Invalid TEXT file_offset %u, lib_size %zu", + text_off, lib_size); + return MOD_TYPE_INVALID; + } + tr_info(&lib_manager_tr, "Module API version: %u.%u.%u, format: 0x%x", build_info->api_version_number.fields.major, build_info->api_version_number.fields.middle, @@ -643,7 +688,8 @@ static struct comp_dev *lib_manager_module_create(const struct comp_driver *drv, mod = (const struct sof_man_module *) ((const uint8_t *)desc + SOF_MAN_MODULE_OFFSET(entry_index)); - const uintptr_t module_entry_point = lib_manager_allocate_module(mod, config, args->data); + const uintptr_t module_entry_point = lib_manager_allocate_module(desc, mod, config, + args->data); if (!module_entry_point) { tr_err(&lib_manager_tr, "lib_manager_allocate_module() failed!"); @@ -843,6 +889,9 @@ static int lib_manager_dma_init(struct lib_manager_dma_ext *dma_ext, uint32_t dm } chan_index = dma_request_channel(dma_ext->dma->z_dev, &dma_id); + if (chan_index < 0) + return chan_index; + dma_ext->chan = &dma_ext->dma->chan[chan_index]; if (!dma_ext->chan) return -EINVAL; @@ -948,9 +997,17 @@ static int lib_manager_store_library(struct lib_manager_dma_ext *dma_ext, void __sparse_cache *library_base_address; const struct sof_man_fw_desc *man_desc = (struct sof_man_fw_desc *) ((__sparse_force uint8_t *)man_buffer + SOF_MAN_ELF_TEXT_OFFSET); - uint32_t preload_size = man_desc->header.preload_page_count * PAGE_SZ; int ret; + /* Zephyr UINT_MAX is explicitly 32 bits, and so is preload_page_count */ + if (man_desc->header.preload_page_count >= UINT_MAX / PAGE_SZ) { + tr_err(&lib_manager_tr, "Invalid preload page count %u.", + man_desc->header.preload_page_count); + return -EINVAL; + } + + uint32_t preload_size = man_desc->header.preload_page_count * PAGE_SZ; + /* * The module manifest structure always has its maximum size regardless of * the actual size of the manifest. @@ -960,10 +1017,10 @@ static int lib_manager_store_library(struct lib_manager_dma_ext *dma_ext, return -EINVAL; } - /* Prepare storage memory, note: it is never freed, library unloading is unsupported */ /* - * Prepare storage memory, note: it is never freed, it is assumed, that this - * memory is abundant, so we store all loaded modules there permanently + * Prepare storage memory, note: it is never freed, it is assumed, that + * this memory is abundant, so we store all loaded modules there + * permanently, unloading is unsupported */ library_base_address = lib_manager_allocate_store_mem(preload_size, 0); if (!library_base_address) @@ -1164,7 +1221,7 @@ int lib_manager_load_library(uint32_t dma_id, uint32_t lib_id, uint32_t type) uint32_t module_id = lib_id << LIB_MANAGER_LIB_ID_SHIFT; const struct sof_man_module *mod = lib_manager_get_module_manifest(module_id); - if (!ret && module_is_llext(mod)) + if (!ret && mod && module_is_llext(mod)) /* Auxiliary LLEXT libraries need to be linked upon loading */ ret = llext_manager_add_library(module_id); diff --git a/src/library_manager/llext_manager.c b/src/library_manager/llext_manager.c index a370a5f1c7f7..4c1e4f02d5b5 100644 --- a/src/library_manager/llext_manager.c +++ b/src/library_manager/llext_manager.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -457,9 +458,22 @@ static int llext_manager_mod_init(struct lib_manager_mod_ctx *ctx, { struct sof_man_module *mod_array = (struct sof_man_module *)((uint8_t *)desc + SOF_MAN_MODULE_OFFSET(0)); + /* preload_page_count was checked when library was loaded */ + size_t lib_size = desc->header.preload_page_count * PAGE_SZ; unsigned int i, n_mod; size_t offs; + /* We'll check overflows below */ + uintptr_t mod_end_addr = (uintptr_t)(mod_array + desc->header.num_module_entries); + uintptr_t img_end_addr = (uintptr_t)desc - SOF_MAN_ELF_TEXT_OFFSET + lib_size; + + if (mod_end_addr < (uintptr_t)mod_array || img_end_addr < (uintptr_t)desc || + mod_end_addr >= img_end_addr) { + tr_err(&lib_manager_tr, "invalid module entry count: %u", + desc->header.num_module_entries); + return -EOVERFLOW; + } + /* count modules */ for (i = 0, n_mod = 0, offs = ~0; i < desc->header.num_module_entries; i++) if (mod_array[i].segment[LIB_MANAGER_TEXT].file_offset != offs) { @@ -1055,9 +1069,13 @@ int llext_manager_add_library(uint32_t module_id) const struct sof_man_fw_desc *desc = lib_manager_get_library_manifest(module_id); unsigned int i; + int ret; - if (!ctx->mod) - llext_manager_mod_init(ctx, desc); + if (!ctx->mod) { + ret = llext_manager_mod_init(ctx, desc); + if (ret < 0) + return ret; + } for (i = 0; i < ctx->n_mod; i++) { const struct sof_man_module *mod = lib_manager_get_module_manifest(module_id + i); @@ -1065,9 +1083,9 @@ int llext_manager_add_library(uint32_t module_id) if (mod->type.load_type == SOF_MAN_MOD_TYPE_LLEXT_AUX) { const struct sof_man_module_manifest *mod_manifest; const struct sof_module_api_build_info *buildinfo; - int ret = llext_manager_link_single(module_id + i, desc, ctx, - (const void **)&buildinfo, &mod_manifest); + ret = llext_manager_link_single(module_id + i, desc, ctx, + (const void **)&buildinfo, &mod_manifest); if (ret < 0) return ret; } diff --git a/src/library_manager/llext_manager_dram.c b/src/library_manager/llext_manager_dram.c index 25c00f47b4e1..2f6cff2b3501 100644 --- a/src/library_manager/llext_manager_dram.c +++ b/src/library_manager/llext_manager_dram.c @@ -200,10 +200,14 @@ int llext_manager_restore_from_dram(void) continue; } - /* Panics on failure - use the same zone as during the first boot */ struct lib_manager_mod_ctx *ctx = rmalloc(SOF_MEM_FLAG_KERNEL | SOF_MEM_FLAG_COHERENT, sizeof(*ctx)); + if (!ctx) { + tr_err(&lib_manager_tr, "library context allocation failure"); + goto nomem; + } + /* Restore the library context */ *ctx = lib_manager_dram.ctx[j++]; diff --git a/src/math/auditory/auditory.c b/src/math/auditory/auditory.c index 57641c46aa5a..0abd10de9308 100644 --- a/src/math/auditory/auditory.c +++ b/src/math/auditory/auditory.c @@ -12,10 +12,13 @@ #include #include #include +#include #include #include #include +LOG_MODULE_REGISTER(math_auditory, CONFIG_SOF_LOG_LEVEL); + #define ONE_Q16 Q_CONVERT_FLOAT(1, 16) #define ONE_Q20 Q_CONVERT_FLOAT(1, 20) #define ONE_Q30 Q_CONVERT_FLOAT(1, 30) @@ -117,6 +120,16 @@ int mod_psy_get_mel_filterbank(struct processing_module *mod, struct psy_mel_fil if (!fb->scratch_data1 || !fb->scratch_data2) return -ENOMEM; + /* fb->mel_bins is used both as the loop bound and as part of the + * (mel_bins + 1) divisor below. Reject non-positive values up front to + * avoid a divide by zero (mel_bins == -1) and to keep mel_step + * meaningful. + */ + if (fb->mel_bins <= 0 || fb->mel_bins > AUDITORY_MAX_MEL_BANDS) { + comp_cl_err(mod->dev, "Invalid mel_bins %d", fb->mel_bins); + return -EINVAL; + } + /* Log power can be log, or log10 or dB, get multiply coef to convert * log to desired format. */ @@ -149,6 +162,17 @@ int mod_psy_get_mel_filterbank(struct processing_module *mod, struct psy_mel_fil mel_start = psy_hz_to_mel(fb->start_freq); mel_end = psy_hz_to_mel(fb->end_freq); mel_step = (mel_end - mel_start) / (fb->mel_bins + 1); + /* delta_cl / delta_rc below are both equal to mel_step; guard against + * a non-positive step (start_freq >= end_freq, or so many bins that + * the integer division truncates to zero) before using them as + * divisors. + */ + if (mel_step <= 0) { + comp_cl_err(mod->dev, "Invalid mel_step %d (start_freq=%d end_freq=%d mel_bins=%d)", + mel_step, fb->start_freq, fb->end_freq, fb->mel_bins); + return -EINVAL; + } + for (i = 0; i < fb->mel_bins; i++) { left_mel = mel_start + i * mel_step; center_mel = mel_start + (i + 1) * mel_step; @@ -160,6 +184,11 @@ int mod_psy_get_mel_filterbank(struct processing_module *mod, struct psy_mel_fil if (fb->slaney_normalize) { left_hz = psy_mel_to_hz(left_mel); right_hz = psy_mel_to_hz(right_mel); + if (right_hz <= left_hz) { + comp_cl_err(mod->dev, "Invalid Hz range left=%d right=%d at mel bin %d", + left_hz, right_hz, i); + return -EINVAL; + } scale = Q_SHIFT_RND(TWO_Q29 / (right_hz - left_hz), 29, 16); /* Q16.16*/ if (i == 0) { scale_inv = Q_SHIFT_LEFT(ONE_Q30 / scale, 14, 16); diff --git a/src/math/fft/fft_common.c b/src/math/fft/fft_common.c index 5ce47acd025a..589571881443 100644 --- a/src/math/fft/fft_common.c +++ b/src/math/fft/fft_common.c @@ -53,6 +53,7 @@ struct fft_plan *fft_plan_common_new(struct processing_module *mod, void *inb, break; default: comp_cl_err(mod->dev, "Invalid word length."); + mod_free(mod, plan); return NULL; } diff --git a/src/math/fft/fft_multi.c b/src/math/fft/fft_multi.c index b16decca012d..794eb3002c47 100644 --- a/src/math/fft/fft_multi.c +++ b/src/math/fft/fft_multi.c @@ -75,7 +75,7 @@ struct fft_multi_plan *mod_fft_multi_plan_new(struct processing_module *mod, voi plan->tmp_i32[0] = mod_balloc(mod, tmp_size); if (!plan->tmp_i32[0]) { comp_cl_err(mod->dev, "Failed to allocate FFT buffers"); - goto err_free_bit_reverse; + goto err; } /* Set up buffers */ @@ -95,7 +95,7 @@ struct fft_multi_plan *mod_fft_multi_plan_new(struct processing_module *mod, voi plan->tmp_o32[i], plan->fft_size, 32); if (!plan->fft_plan[i]) - goto err_free_buffer; + goto err; plan->fft_plan[i]->bit_reverse_idx = plan->bit_reverse_idx; } @@ -110,14 +110,13 @@ struct fft_multi_plan *mod_fft_multi_plan_new(struct processing_module *mod, voi plan->fft_plan[0]->len); return plan; -err_free_buffer: - mod_free(mod, plan->tmp_i32[0]); - -err_free_bit_reverse: - mod_free(mod, plan->bit_reverse_idx); - err: - mod_free(mod, plan); + /* mod_fft_multi_plan_free() handles partial state safely: + * - fft_plan[i] entries left NULL by mod_zalloc are skipped by mod_free, + * - tmp_i32[0] is freed only when num_ffts > 1, so it does not free + * the caller-provided inb in the single-FFT case. + */ + mod_fft_multi_plan_free(mod, plan); return NULL; } diff --git a/src/math/iir_df1.c b/src/math/iir_df1.c index 861c5cd2cf19..5e4d13b8cc70 100644 --- a/src/math/iir_df1.c +++ b/src/math/iir_df1.c @@ -16,9 +16,13 @@ int iir_delay_size_df1(struct sof_eq_iir_header *config) { - int n = config->num_sections; /* One section uses two unit delays */ + uint32_t n = config->num_sections; /* One section uses four unit delays */ - if (n > SOF_EQ_IIR_BIQUADS_MAX || n < 1) + if (!n || n > SOF_EQ_IIR_BIQUADS_MAX) + return -EINVAL; + + if (!config->num_sections_in_series || + config->num_sections_in_series > n) return -EINVAL; return 4 * n * sizeof(int32_t); diff --git a/src/math/iir_df2t.c b/src/math/iir_df2t.c index b2adb69be03f..ed2b50a21a4e 100644 --- a/src/math/iir_df2t.c +++ b/src/math/iir_df2t.c @@ -16,9 +16,13 @@ int iir_delay_size_df2t(struct sof_eq_iir_header *config) { - int n = config->num_sections; /* One section uses two unit delays */ + uint32_t n = config->num_sections; /* One section uses two unit delays */ - if (n > SOF_EQ_IIR_BIQUADS_MAX || n < 1) + if (!n || n > SOF_EQ_IIR_BIQUADS_MAX) + return -EINVAL; + + if (!config->num_sections_in_series || + config->num_sections_in_series > n) return -EINVAL; return 2 * n * sizeof(int64_t); diff --git a/src/module/audio/sink_api.c b/src/module/audio/sink_api.c index c0a94031f105..969b37f5a987 100644 --- a/src/module/audio/sink_api.c +++ b/src/module/audio/sink_api.c @@ -109,6 +109,11 @@ EXPORT_SYMBOL(sink_get_frame_bytes); size_t sink_get_free_frames(struct sof_sink *sink) { + /* The frame size is a valid divisor: a host channel count of zero is + * rejected at module init (module_adapter_ipc4.c) before it reaches + * the stream, and the sample size is fixed by a valid frame format, so + * this is not re-checked on the hot path. + */ return sink_get_free_size(sink) / sink_get_frame_bytes(sink); } EXPORT_SYMBOL(sink_get_free_frames); diff --git a/src/platform/amd/acp_6_3/include/platform/lib/memory.h b/src/platform/amd/acp_6_3/include/platform/lib/memory.h index 2597b0858715..2b4fc43e6322 100644 --- a/src/platform/amd/acp_6_3/include/platform/lib/memory.h +++ b/src/platform/amd/acp_6_3/include/platform/lib/memory.h @@ -165,11 +165,6 @@ struct sof; #define SHARED_DATA void platform_init_memmap(struct sof *sof); -static inline void *platform_shared_get(void *ptr, int bytes) -{ - return ptr; -} - static inline void *platform_rfree_prepare(void *ptr) { return ptr; diff --git a/src/platform/amd/acp_7_0/include/platform/lib/memory.h b/src/platform/amd/acp_7_0/include/platform/lib/memory.h index de506811721d..60a72e9f38c1 100644 --- a/src/platform/amd/acp_7_0/include/platform/lib/memory.h +++ b/src/platform/amd/acp_7_0/include/platform/lib/memory.h @@ -176,11 +176,6 @@ struct sof; #define SHARED_DATA void platform_init_memmap(struct sof *sof); -static inline void *platform_shared_get(void *ptr, int bytes) -{ - return ptr; -} - static inline void *platform_rfree_prepare(void *ptr) { return ptr; diff --git a/src/platform/amd/acp_7_0/include/platform/platform_misc.h b/src/platform/amd/acp_7_0/include/platform/platform_misc.h index 2338a91317e0..83eb206ee2e9 100644 --- a/src/platform/amd/acp_7_0/include/platform/platform_misc.h +++ b/src/platform/amd/acp_7_0/include/platform/platform_misc.h @@ -422,10 +422,13 @@ struct sdw_pin_data { }; struct tdm_context { + uint64_t prev_pos; + uint32_t buff_size; uint32_t tdm_instance; uint32_t pin_dir; uint32_t dma_channel; uint32_t index; + uint32_t frame_fmt; }; struct dmic_context { diff --git a/src/platform/amd/acp_7_x/include/arch/xtensa/config/core-isa.h b/src/platform/amd/acp_7_x/include/arch/xtensa/config/core-isa.h deleted file mode 100644 index fbf0c967fd6c..000000000000 --- a/src/platform/amd/acp_7_x/include/arch/xtensa/config/core-isa.h +++ /dev/null @@ -1,757 +0,0 @@ -/* - * xtensa/config/core-isa.h -- HAL definitions that are dependent on Xtensa - * processor CORE configuration - * - * See , which includes this file, for more details. - */ - -/* Xtensa processor core configuration information. - - Customer ID=20511; Build=0xb6494; Copyright (c) 1999-2024 Tensilica Inc. - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - -#ifndef XTENSA_CORE_CONFIGURATION_H_ -#define XTENSA_CORE_CONFIGURATION_H_ - - -/**************************************************************************** - Parameters Useful for Any Code, USER or PRIVILEGED - ****************************************************************************/ - -/* - * Note: Macros of the form XCHAL_HAVE_*** have a value of 1 if the option is - * configured, and a value of 0 otherwise. These macros are always defined. - */ - - -/*---------------------------------------------------------------------- - ISA - ----------------------------------------------------------------------*/ - -#define XCHAL_HAVE_BE 0 /* big-endian byte ordering */ -#define XCHAL_HAVE_WINDOWED 1 /* windowed registers option */ -#define XCHAL_NUM_AREGS 64 /* num of physical addr regs */ -#define XCHAL_NUM_AREGS_LOG2 6 /* log2(XCHAL_NUM_AREGS) */ -#define XCHAL_MAX_INSTRUCTION_SIZE 16 /* max instr bytes (3..8) */ -#define XCHAL_HAVE_DEBUG 1 /* debug option */ -#define XCHAL_HAVE_DENSITY 1 /* 16-bit instructions */ -#define XCHAL_HAVE_LOOPS 1 /* zero-overhead loops */ -#define XCHAL_LOOP_BUFFER_SIZE 256 /* zero-ov. loop instr buffer size */ -#define XCHAL_HAVE_NSA 1 /* NSA/NSAU instructions */ -#define XCHAL_HAVE_MINMAX 1 /* MIN/MAX instructions */ -#define XCHAL_HAVE_SEXT 1 /* SEXT instruction */ -#define XCHAL_HAVE_DEPBITS 0 /* DEPBITS instruction */ -#define XCHAL_HAVE_CLAMPS 1 /* CLAMPS instruction */ -#define XCHAL_HAVE_MUL16 1 /* MUL16S/MUL16U instructions */ -#define XCHAL_HAVE_MUL32 1 /* MULL instruction */ -#define XCHAL_HAVE_MUL32_HIGH 1 /* MULUH/MULSH instructions */ -#define XCHAL_HAVE_DIV32 1 /* QUOS/QUOU/REMS/REMU instructions */ -#define XCHAL_HAVE_L32R 1 /* L32R instruction */ -#define XCHAL_HAVE_ABSOLUTE_LITERALS 0 /* non-PC-rel (extended) L32R */ -#define XCHAL_HAVE_CONST16 0 /* CONST16 instruction */ -#define XCHAL_HAVE_ADDX 1 /* ADDX#/SUBX# instructions */ -#define XCHAL_HAVE_EXCLUSIVE 0 /* L32EX/S32EX instructions */ -#define XCHAL_HAVE_WIDE_BRANCHES 0 /* B*.W18 or B*.W15 instr's */ -#define XCHAL_HAVE_PREDICTED_BRANCHES 0 /* B[EQ/EQZ/NE/NEZ]T instr's */ -#define XCHAL_HAVE_CALL4AND12 1 /* (obsolete option) */ -#define XCHAL_HAVE_ABS 1 /* ABS instruction */ -#define XCHAL_HAVE_RELEASE_SYNC 1 /* L32AI/S32RI instructions */ -#define XCHAL_HAVE_S32C1I 0 /* S32C1I instruction */ -#define XCHAL_HAVE_SPECULATION 0 /* speculation */ -#define XCHAL_HAVE_FULL_RESET 1 /* all regs/state reset */ -#define XCHAL_NUM_CONTEXTS 1 /* */ -#define XCHAL_NUM_MISC_REGS 2 /* num of scratch regs (0..4) */ -#define XCHAL_HAVE_TAP_MASTER 0 /* JTAG TAP control instr's */ -#define XCHAL_HAVE_PRID 1 /* processor ID register */ -#define XCHAL_HAVE_EXTERN_REGS 1 /* WER/RER instructions */ -#define XCHAL_HAVE_MX 0 /* MX core (Tensilica internal) */ -#define XCHAL_HAVE_MP_INTERRUPTS 0 /* interrupt distributor port */ -#define XCHAL_HAVE_MP_RUNSTALL 0 /* core RunStall control port */ -#define XCHAL_HAVE_PSO 0 /* Power Shut-Off */ -#define XCHAL_HAVE_PSO_CDM 0 /* core/debug/mem pwr domains */ -#define XCHAL_HAVE_PSO_FULL_RETENTION 0 /* all regs preserved on PSO */ -#define XCHAL_HAVE_THREADPTR 1 /* THREADPTR register */ -#define XCHAL_HAVE_BOOLEANS 1 /* boolean registers */ -#define XCHAL_HAVE_CP 1 /* CPENABLE reg (coprocessor) */ -#define XCHAL_CP_MAXCFG 2 /* max allowed cp id plus one */ -#define XCHAL_HAVE_MAC16 0 /* MAC16 package */ -#define XCHAL_HAVE_LX 1 /* LX core */ -#define XCHAL_HAVE_NX 0 /* NX core (starting RH) */ -#define XCHAL_HAVE_RNX 0 /* RNX core (starting RJ) */ - -#define XCHAL_HAVE_SUPERGATHER 0 /* SuperGather */ - -#define XCHAL_HAVE_FUSION 0 /* Fusion */ -#define XCHAL_HAVE_FUSION_FP 0 /* Fusion FP option */ -#define XCHAL_HAVE_FUSION_LOW_POWER 0 /* Fusion Low Power option */ -#define XCHAL_HAVE_FUSION_AES 0 /* Fusion BLE/Wifi AES-128 CCM option */ -#define XCHAL_HAVE_FUSION_CONVENC 0 /* Fusion Conv Encode option */ -#define XCHAL_HAVE_FUSION_LFSR_CRC 0 /* Fusion LFSR-CRC option */ -#define XCHAL_HAVE_FUSION_BITOPS 0 /* Fusion Bit Operations Support option */ -#define XCHAL_HAVE_FUSION_AVS 0 /* Fusion AVS option */ -#define XCHAL_HAVE_FUSION_16BIT_BASEBAND 0 /* Fusion 16-bit Baseband option */ -#define XCHAL_HAVE_FUSION_VITERBI 0 /* Fusion Viterbi option */ -#define XCHAL_HAVE_FUSION_SOFTDEMAP 0 /* Fusion Soft Bit Demap option */ -#define XCHAL_HAVE_HIFIPRO 0 /* HiFiPro Audio Engine pkg */ -#define XCHAL_HAVE_HIFI5 1 /* HiFi5 Audio Engine pkg */ -#define XCHAL_HAVE_HIFI5_NN_MAC 1 /* HiFi5 Audio Engine NN-MAC option */ -#define XCHAL_HAVE_HIFI5_VFPU 1 /* HiFi5 Audio Engine Single-Precision VFPU option */ -#define XCHAL_HAVE_HIFI5_HP_VFPU 1 /* HiFi5 Audio Engine Half-Precision VFPU option */ -#define XCHAL_HAVE_HIFI4 0 /* HiFi4 Audio Engine pkg */ -#define XCHAL_HAVE_HIFI4_VFPU 1 /* HiFi4 Audio Engine VFPU option */ -#define XCHAL_HAVE_HIFI3 1 /* HiFi3 Audio Engine pkg */ -#define XCHAL_HAVE_HIFI3_VFPU 1 /* HiFi3 Audio Engine VFPU option */ -#define XCHAL_HAVE_HIFI3Z 0 /* HiFi3Z Audio Engine pkg */ -#define XCHAL_HAVE_HIFI3Z_VFPU 0 /* HiFi3Z Audio Engine VFPU option */ -#define XCHAL_HAVE_HIFI1 0 /* HiFi1 */ -#define XCHAL_HAVE_HIFI1_VFPU 0 /* HiFi1 VFPU option */ -#define XCHAL_HAVE_HIFI1_LOW_LATENCY_MAC_FMA 0 /* HiFi1 Low-latency MAC/FMA option */ -#define XCHAL_HAVE_HIFI2 0 /* HiFi2 Audio Engine pkg */ -#define XCHAL_HAVE_HIFI2EP 0 /* HiFi2EP */ -#define XCHAL_HAVE_HIFI_MINI 0 - - - -#define XCHAL_HAVE_VECTORFPU2005 0 /* vector floating-point pkg */ -#define XCHAL_HAVE_USER_DPFPU 0 /* user DP floating-point pkg */ -#define XCHAL_HAVE_USER_SPFPU 1 /* user SP floating-point pkg */ -#define XCHAL_HAVE_FP 1 /* single prec floating point */ -#define XCHAL_HAVE_FP_DIV 1 /* FP with DIV instructions */ -#define XCHAL_HAVE_FP_RECIP 1 /* FP with RECIP instructions */ -#define XCHAL_HAVE_FP_SQRT 1 /* FP with SQRT instructions */ -#define XCHAL_HAVE_FP_RSQRT 1 /* FP with RSQRT instructions */ -#define XCHAL_HAVE_DFP 0 /* double precision FP pkg */ -#define XCHAL_HAVE_DFP_DIV 0 /* DFP with DIV instructions */ -#define XCHAL_HAVE_DFP_RECIP 0 /* DFP with RECIP instructions*/ -#define XCHAL_HAVE_DFP_SQRT 0 /* DFP with SQRT instructions */ -#define XCHAL_HAVE_DFP_RSQRT 0 /* DFP with RSQRT instructions*/ -#define XCHAL_HAVE_DFP_ACCEL 0 /* double precision FP acceleration pkg */ -#define XCHAL_HAVE_DFP_accel XCHAL_HAVE_DFP_ACCEL /* for backward compatibility */ - -#define XCHAL_HAVE_DFPU_SINGLE_ONLY 0 /* DFPU Coprocessor, single precision only */ -#define XCHAL_HAVE_DFPU_SINGLE_DOUBLE 0 /* DFPU Coprocessor, single and double precision */ -#define XCHAL_HAVE_VECTRA1 0 /* Vectra I pkg */ -#define XCHAL_HAVE_VECTRALX 0 /* Vectra LX pkg */ - -#define XCHAL_HAVE_FUSIONG 0 /* FusionG */ -#define XCHAL_HAVE_FUSIONG3 0 /* FusionG3 */ -#define XCHAL_HAVE_FUSIONG6 0 /* FusionG6 */ -#define XCHAL_HAVE_FUSIONG_SP_VFPU 0 /* sp_vfpu option on FusionG */ -#define XCHAL_HAVE_FUSIONG_DP_VFPU 0 /* dp_vfpu option on FusionG */ -#define XCHAL_FUSIONG_SIMD32 0 /* simd32 for FusionG */ - -#define XCHAL_HAVE_FUSIONJ 0 /* FusionJ */ -#define XCHAL_HAVE_FUSIONJ6 0 /* FusionJ6 */ -#define XCHAL_HAVE_FUSIONJ_SP_VFPU 0 /* sp_vfpu option on FusionJ */ -#define XCHAL_HAVE_FUSIONJ_DP_VFPU 0 /* dp_vfpu option on FusionJ */ -#define XCHAL_FUSIONJ_SIMD32 0 /* simd32 for FusionJ */ - -#define XCHAL_HAVE_PDX 0 /* PDX-LX */ -#define XCHAL_PDX_SIMD32 0 /* simd32 for PDX */ -#define XCHAL_HAVE_PDX4 0 /* PDX4-LX */ -#define XCHAL_HAVE_PDX8 0 /* PDX8-LX */ -#define XCHAL_HAVE_PDX16 0 /* PDX16-LX */ -#define XCHAL_HAVE_PDXNX 0 /* PDX-NX */ - -#define XCHAL_HAVE_CONNXD2 0 /* ConnX D2 pkg */ -#define XCHAL_HAVE_CONNXD2_DUALLSFLIX 0 /* ConnX D2 & Dual LoadStore Flix */ -#define XCHAL_HAVE_BALL 0 -#define XCHAL_HAVE_BALLAP 0 -#define XCHAL_HAVE_BBE16 0 /* ConnX BBE16 pkg */ -#define XCHAL_HAVE_BBE16_RSQRT 0 /* BBE16 & vector recip sqrt */ -#define XCHAL_HAVE_BBE16_VECDIV 0 /* BBE16 & vector divide */ -#define XCHAL_HAVE_BBE16_DESPREAD 0 /* BBE16 & despread */ -#define XCHAL_HAVE_CONNX_B10 0 /* ConnX B10 pkg*/ -#define XCHAL_HAVE_CONNX_B20 0 /* ConnX B20 pkg*/ -#define XCHAL_HAVE_CONNX_B_DP_VFPU 0 /* Double-precision Vector Floating-point option on ConnX B10 & B20 */ -#define XCHAL_HAVE_CONNX_B_DPX_VFPU 0 /* Double-precision Vector Floating-point option on FP Machine*/ -#define XCHAL_HAVE_CONNX_B_SP_VFPU 0 /* Single-precision Vector Floating-point option on ConnX B10 & B20 */ -#define XCHAL_HAVE_CONNX_B_SPX_VFPU 0 /* Single-precision Extended Vector Floating-point option on ConnX B10 & B20 */ -#define XCHAL_HAVE_CONNX_B_HP_VFPU 0 /* Half-precision Vector Floating-point option on ConnX B10 & B20 */ -#define XCHAL_HAVE_CONNX_B_HPX_VFPU 0 /* Half-precision Extended Vector Floating-point option on ConnX B10 & B20 */ -#define XCHAL_HAVE_CONNX_B_32B_MAC 0 /* 32-bit vector MAC (real and complex), FIR & FFT option on ConnX B10 & B20 */ -#define XCHAL_HAVE_CONNX_B_VITERBI 0 /* Viterbi option on ConnX B10 & B20 */ -#define XCHAL_HAVE_CONNX_B_TURBO 0 /* Turbo option on ConnX B10 & B20 */ -#define XCHAL_HAVE_CONNX_B_LDPC 0 /* LDPC option on ConnX B10 & B20 */ -#define XCHAL_HAVE_BBENEP 0 /* ConnX BBENEP pkgs */ -#define XCHAL_HAVE_BBENEP_SP_VFPU 0 /* sp_vfpu option on BBE-EP */ -#define XCHAL_HAVE_BSP3 0 /* ConnX BSP3 pkg */ -#define XCHAL_HAVE_BSP3_TRANSPOSE 0 /* BSP3 & transpose32x32 */ -#define XCHAL_HAVE_SSP16 0 /* ConnX SSP16 pkg */ -#define XCHAL_HAVE_SSP16_VITERBI 0 /* SSP16 & viterbi */ -#define XCHAL_HAVE_TURBO16 0 /* ConnX Turbo16 pkg */ -#define XCHAL_HAVE_BBP16 0 /* ConnX BBP16 pkg */ -#define XCHAL_HAVE_FLIX3 0 /* basic 3-way FLIX option */ -#define XCHAL_HAVE_GRIVPEP 0 /* General Release of IVPEP */ -#define XCHAL_HAVE_GRIVPEP_HISTOGRAM 0 /* Histogram option on GRIVPEP */ - -#define XCHAL_HAVE_VISION 0 /* Vision P5/P6 */ -#define XCHAL_VISION_SIMD16 0 /* simd16 for Vision P5/P6 */ -#define XCHAL_VISION_TYPE 0 /* Vision P5, P6, Q6, Q7 or Q8 */ -#define XCHAL_VISION_QUAD_MAC_TYPE 0 /* quad_mac option on Vision P6 */ -#define XCHAL_HAVE_VISION_HISTOGRAM 0 /* histogram option on Vision P5/P6 */ -#define XCHAL_HAVE_VISION_DP_VFPU 0 /* dp_vfpu option on Vision Q7/Q8 */ -#define XCHAL_HAVE_VISION_SP_VFPU 0 /* sp_vfpu option on Vision P5/P6/Q6/Q7 */ -#define XCHAL_HAVE_VISION_SP_VFPU_2XFMAC 0 /* sp_vfpu_2xfma option on Vision Q7 */ -#define XCHAL_HAVE_VISION_HP_VFPU 0 /* hp_vfpu option on Vision P6/Q6 */ -#define XCHAL_HAVE_VISION_HP_VFPU_2XFMAC 0 /* hp_vfpu_2xfma option on Vision Q7 */ - -#define XCHAL_HAVE_VISIONC 0 /* Vision C */ - -#define XCHAL_HAVE_XNNE 1 /* XNNE */ -#define XCHAL_XNNE_VERSION 2 /* XNNE version */ -#define XCHAL_XNNE_PADDR 0x20000000 /* Base address */ -#define XCHAL_XNNE_NUM_SBLKS 1 /* SBLK count */ -#define XCHAL_XNNE_MBLKS_PER_SBLK 4 /* MBLKs per SBLK */ -#define XCHAL_XNNE_IBUF_SIZE 0 /* IBuf size */ -#define XCHAL_XNNE_OBUF_SIZE 0 /* OBuf size */ -#define XCHAL_XNNE_CBUF_SIZE 0 /* CBuf size */ -#define XCHAL_XNNE_UBUF_SIZE 131072 /* UBuf size */ -#define XCHAL_XNNE_AXIM_DATA_WIDTH 128 /* Data Width */ - - -/*---------------------------------------------------------------------- - MISC - ----------------------------------------------------------------------*/ - -#define XCHAL_NUM_LOADSTORE_UNITS 2 /* load/store units */ -#define XCHAL_NUM_WRITEBUFFER_ENTRIES 16 /* size of write buffer */ -#define XCHAL_INST_FETCH_WIDTH 16 /* instr-fetch width in bytes */ -#define XCHAL_DATA_WIDTH 16 /* data width in bytes */ -#define XCHAL_DATA_PIPE_DELAY 2 /* d-side pipeline delay - (1 = 5-stage, 2 = 7-stage) */ -#define XCHAL_CLOCK_GATING_GLOBAL 1 /* global clock gating */ -#define XCHAL_CLOCK_GATING_FUNCUNIT 1 /* funct. unit clock gating */ -/* In T1050, applies to selected core load and store instructions (see ISA): */ -#define XCHAL_UNALIGNED_LOAD_EXCEPTION 1 /* unaligned loads cause exc. */ -#define XCHAL_UNALIGNED_STORE_EXCEPTION 1 /* unaligned stores cause exc.*/ -#define XCHAL_UNALIGNED_LOAD_HW 0 /* unaligned loads work in hw */ -#define XCHAL_UNALIGNED_STORE_HW 0 /* unaligned stores work in hw*/ - -#define XCHAL_UNIFIED_LOADSTORE 0 - -#define XCHAL_SW_VERSION 1409000 /* sw version of this header */ -#define XCHAL_SW_VERSION_MAJOR 14000 /* major ver# of sw */ -#define XCHAL_SW_VERSION_MINOR 9 /* minor ver# of sw */ -#define XCHAL_SW_VERSION_MICRO 0 /* micro ver# of sw */ -#define XCHAL_SW_MINOR_VERSION 1409000 /* with zeroed micro */ -#define XCHAL_SW_MICRO_VERSION 1409000 - -#define XCHAL_CORE_ID "ACP73x_HiFi5_NNE_PROD" /* alphanum core name - (CoreID) set in the Xtensa - Processor Generator */ - -#define XCHAL_BUILD_UNIQUE_ID 0x000B6494 /* 22-bit sw build ID */ - -/* - * These definitions describe the hardware targeted by this software. - */ -#define XCHAL_HW_CONFIGID0 0xC0039286 /* ConfigID hi 32 bits*/ -#define XCHAL_HW_CONFIGID1 0x2A4B6494 /* ConfigID lo 32 bits*/ -#define XCHAL_HW_VERSION_NAME "LX7.1.9" /* full version name */ -#define XCHAL_HW_VERSION_MAJOR 2810 /* major ver# of targeted hw */ -#define XCHAL_HW_VERSION_MINOR 9 /* minor ver# of targeted hw */ -#define XCHAL_HW_VERSION_MICRO 0 /* subdot ver# of targeted hw */ -#define XCHAL_HW_VERSION 281090 /* major*100+(major<2810 ? minor : minor*10+micro) */ -#define XCHAL_HW_REL_LX7 1 -#define XCHAL_HW_REL_LX7_1 1 -#define XCHAL_HW_REL_LX7_1_9 1 -#define XCHAL_HW_CONFIGID_RELIABLE 1 -/* If software targets a *range* of hardware versions, these are the bounds: */ -#define XCHAL_HW_MIN_VERSION_MAJOR 2810 /* major v of earliest tgt hw */ -#define XCHAL_HW_MIN_VERSION_MINOR 9 /* minor v of earliest tgt hw */ -#define XCHAL_HW_MIN_VERSION_MICRO 0 /* micro v of earliest tgt hw */ -#define XCHAL_HW_MIN_VERSION 281090 /* earliest targeted hw */ -#define XCHAL_HW_MAX_VERSION_MAJOR 2810 /* major v of latest tgt hw */ -#define XCHAL_HW_MAX_VERSION_MINOR 9 /* minor v of latest tgt hw */ -#define XCHAL_HW_MAX_VERSION_MICRO 0 /* micro v of latest tgt hw */ -#define XCHAL_HW_MAX_VERSION 281090 /* latest targeted hw */ - -/* Config is enabled for functional safety: */ -#define XCHAL_HAVE_FUNC_SAFETY 0 - -/* Config is enabled for secure operation: */ -#define XCHAL_HAVE_SECURE 0 - -#define XCHAL_HAVE_APB 0 - -/*---------------------------------------------------------------------- - CACHE - ----------------------------------------------------------------------*/ - -#define XCHAL_ICACHE_LINESIZE 128 /* I-cache line size in bytes */ -#define XCHAL_DCACHE_LINESIZE 128 /* D-cache line size in bytes */ -#define XCHAL_ICACHE_LINEWIDTH 7 /* log2(I line size in bytes) */ -#define XCHAL_DCACHE_LINEWIDTH 7 /* log2(D line size in bytes) */ - -#define XCHAL_ICACHE_SIZE 131072 /* I-cache size in bytes or 0 */ -#define XCHAL_ICACHE_SIZE_LOG2 17 -#define XCHAL_DCACHE_SIZE 131072 /* D-cache size in bytes or 0 */ -#define XCHAL_DCACHE_SIZE_LOG2 17 - -#define XCHAL_DCACHE_IS_WRITEBACK 1 /* writeback feature */ -#define XCHAL_DCACHE_IS_COHERENT 0 /* MP coherence feature */ - -#define XCHAL_HAVE_PREFETCH 1 /* PREFCTL register */ -#define XCHAL_HAVE_PREFETCH_L1 1 /* prefetch to L1 cache */ -#define XCHAL_PREFETCH_CASTOUT_LINES 2 /* dcache pref. castout bufsz */ -#define XCHAL_PREFETCH_ENTRIES 16 /* cache prefetch entries */ -#define XCHAL_PREFETCH_BLOCK_ENTRIES 0 /* prefetch block streams */ -#define XCHAL_HAVE_CACHE_BLOCKOPS 0 /* block prefetch for caches */ -#define XCHAL_HAVE_CME_DOWNGRADES 0 -#define XCHAL_HAVE_ICACHE_TEST 1 /* Icache test instructions */ -#define XCHAL_HAVE_DCACHE_TEST 1 /* Dcache test instructions */ -#define XCHAL_HAVE_ICACHE_DYN_WAYS 1 /* Icache dynamic way support */ -#define XCHAL_HAVE_DCACHE_DYN_WAYS 1 /* Dcache dynamic way support */ -#define XCHAL_HAVE_ICACHE_DYN_ENABLE 1 /* Icache enabled via MEMCTL */ -#define XCHAL_HAVE_DCACHE_DYN_ENABLE 1 /* Dcache enabled via MEMCTL */ - -#define XCHAL_L1SCACHE_SIZE 0 -#define XCHAL_L1SCACHE_SIZE_LOG2 0 -#define XCHAL_L1SCACHE_WAYS 1 -#define XCHAL_L1SCACHE_WAYS_LOG2 0 -#define XCHAL_L1SCACHE_ACCESS_SIZE 0 -#define XCHAL_L1SCACHE_BANKS 1 - -#define XCHAL_L1VCACHE_SIZE 0 - -#define XCHAL_HAVE_L2 0 /* NX L2 cache controller */ -#define XCHAL_HAVE_L2_CACHE 0 -#define XCHAL_NUM_CORES_IN_CLUSTER 0 - -/* PRID_ID macros are for internal use only ... subject to removal */ -#define PRID_ID_SHIFT 0 -#define PRID_ID_BITS 4 -#define PRID_ID_MASK 0x0000000F - -/* This one is a form of caching, though not architecturally visible: */ -#define XCHAL_HAVE_BRANCH_PREDICTION 0 /* branch [target] prediction */ - - - - -/**************************************************************************** - Parameters Useful for PRIVILEGED (Supervisory or Non-Virtualized) Code - ****************************************************************************/ - - -#ifndef XTENSA_HAL_NON_PRIVILEGED_ONLY - -/*---------------------------------------------------------------------- - CACHE - ----------------------------------------------------------------------*/ - -#define XCHAL_HAVE_PIF 1 /* any outbound bus present */ - -#define XCHAL_HAVE_AXI 1 /* AXI bus */ -#define XCHAL_HAVE_AXI_ECC 1 /* ECC on AXI bus */ -#define XCHAL_HAVE_ACELITE 0 /* ACELite bus */ - -#define XCHAL_HAVE_PIF_WR_RESP 1 /* pif write response */ -#define XCHAL_HAVE_PIF_REQ_ATTR 1 /* pif attribute */ - -/* If present, cache size in bytes == (ways * 2^(linewidth + setwidth)). */ - -/* Number of cache sets in log2(lines per way): */ -#define XCHAL_ICACHE_SETWIDTH 8 -#define XCHAL_DCACHE_SETWIDTH 8 - -/* Cache set associativity (number of ways): */ -#define XCHAL_ICACHE_WAYS 4 -#define XCHAL_ICACHE_WAYS_LOG2 2 -#define XCHAL_DCACHE_WAYS 4 -#define XCHAL_DCACHE_WAYS_LOG2 2 - -/* Cache features: */ -#define XCHAL_ICACHE_LINE_LOCKABLE 1 -#define XCHAL_DCACHE_LINE_LOCKABLE 1 -#define XCHAL_ICACHE_ECC_PARITY 0 -#define XCHAL_DCACHE_ECC_PARITY 0 -#define XCHAL_ICACHE_ECC_WIDTH 4 -#define XCHAL_DCACHE_ECC_WIDTH 1 - -/* Cache access size in bytes (affects operation of SICW instruction): */ -#define XCHAL_ICACHE_ACCESS_SIZE 16 -#define XCHAL_DCACHE_ACCESS_SIZE 16 - -#define XCHAL_DCACHE_BANKS 2 /* number of banks */ - -/* The number of Cache lines associated with a single cache tag */ -#define XCHAL_DCACHE_LINES_PER_TAG_LOG2 0 - -/* Number of encoded cache attr bits (see for decoded bits): */ - -/* Extended memory attributes supported. */ -#define XCHAL_HAVE_EXT_CA 0 - - -/*---------------------------------------------------------------------- - INTERNAL I/D RAM/ROMs and XLMI - ----------------------------------------------------------------------*/ -#define XCHAL_NUM_INSTROM 0 /* number of core instr. ROMs */ -#define XCHAL_NUM_INSTRAM 1 /* number of core instr. RAMs */ -#define XCHAL_NUM_DATAROM 0 /* number of core data ROMs */ -#define XCHAL_NUM_DATARAM 1 /* number of core data RAMs */ -#define XCHAL_NUM_URAM 0 /* number of core unified RAMs*/ -#define XCHAL_NUM_XLMI 0 /* number of core XLMI ports */ -#define XCHAL_HAVE_IRAMCFG 0 /* IRAMxCFG register present */ -#define XCHAL_HAVE_DRAMCFG 0 /* DRAMxCFG register present */ - -/* Instruction RAM 0: */ -#define XCHAL_INSTRAM0_VADDR 0x7F000000 /* virtual address */ -#define XCHAL_INSTRAM0_PADDR 0x7F000000 /* physical address */ -#define XCHAL_INSTRAM0_SIZE 524288 /* size in bytes */ -#define XCHAL_INSTRAM0_ECC_PARITY 0 /* ECC/parity type, 0=none */ -#define XCHAL_HAVE_INSTRAM0 1 -#define XCHAL_INSTRAM0_HAVE_IDMA 0 /* idma supported by this local memory */ - -/* Data RAM 0: */ -#define XCHAL_DATARAM0_VADDR 0xE0000000 /* virtual address */ -#define XCHAL_DATARAM0_PADDR 0xE0000000 /* physical address */ -#define XCHAL_DATARAM0_SIZE 524288 /* size in bytes */ -#define XCHAL_DATARAM0_ECC_PARITY 0 /* ECC/parity type, 0=none */ -#define XCHAL_DATARAM0_BANKS 2 /* number of banks */ -#define XCHAL_HAVE_DATARAM0 1 -#define XCHAL_DATARAM0_HAVE_IDMA 1 /* idma supported by this local memory */ - -#define XCHAL_HAVE_IMEM_LOADSTORE 1 /* can load/store to IROM/IRAM*/ - - -/*---------------------------------------------------------------------- - IDMA - ----------------------------------------------------------------------*/ - -#define XCHAL_HAVE_IDMA 0 -#define XCHAL_IDMA_NUM_CHANNELS 1 /* number of channels */ -#define XCHAL_IDMA_ADDR_WIDTH 32 /* address width in bits */ -#define XCHAL_IDMA_DATA_WIDTH 128 /* data width in bits */ -#define XCHAL_IDMA_DESC_SIZE 32 /* max descriptor size */ -#define XCHAL_IDMA_MAX_OUTSTANDING_REQ 64 /* max outstanding requests */ -#define XCHAL_IDMA_HAVE_REORDERBUF 0 /* has reorder buffer */ -#define XCHAL_IDMA_HAVE_TRANSPOSE 0 /* has transpose function */ -#define XCHAL_HAVE_IDMA_TRANSPOSE 0 /* Deprecated */ -#define XCHAL_IDMA_NUM_AXI2AXI_CHAN 0 /* how many channels support AXI-to-AXI transfers */ -#define XCHAL_IDMA_ID_PER_CHANNEL 0 - -#define XCHAL_IDMA_CH0_DONE_INTERRUPT 9 -#define XCHAL_IDMA_CH0_ERR_INTERRUPT 10 - - -/*---------------------------------------------------------------------- - INTERRUPTS and TIMERS - ----------------------------------------------------------------------*/ - -#define XCHAL_HAVE_INTERRUPTS 1 /* interrupt option */ -#define XCHAL_HAVE_NMI 1 /* non-maskable interrupt */ -#define XCHAL_HAVE_CCOUNT 1 /* CCOUNT reg. (timer option) */ -#define XCHAL_NUM_TIMERS 2 /* number of CCOMPAREn regs */ -#define XCHAL_NUM_INTERRUPTS 13 /* number of interrupts */ -#define XCHAL_NUM_INTERRUPTS_LOG2 4 /* ceil(log2(NUM_INTERRUPTS)) */ -#define XCHAL_NUM_EXTINTERRUPTS 7 /* num of external interrupts */ -#define XCHAL_NUM_INTLEVELS 6 /* number of interrupt levels - (not including level zero) */ - - -#define XCHAL_HAVE_HIGHPRI_INTERRUPTS 1 /* med/high-pri. interrupts */ -#define XCHAL_EXCM_LEVEL 5 /* level masked by PS.EXCM */ - /* (always 1 in XEA1; levels 2 .. EXCM_LEVEL are "medium priority") */ - -/* Masks of interrupts at each interrupt level: */ -#define XCHAL_INTLEVEL1_MASK 0x00001F43 -#define XCHAL_INTLEVEL2_MASK 0x00000004 -#define XCHAL_INTLEVEL3_MASK 0x00000008 -#define XCHAL_INTLEVEL4_MASK 0x00000010 -#define XCHAL_INTLEVEL5_MASK 0x00000020 -#define XCHAL_INTLEVEL6_MASK 0x00000000 -#define XCHAL_INTLEVEL7_MASK 0x00000080 - -/* Masks of interrupts at each range 1..n of interrupt levels: */ -#define XCHAL_INTLEVEL1_ANDBELOW_MASK 0x00001F43 -#define XCHAL_INTLEVEL2_ANDBELOW_MASK 0x00001F47 -#define XCHAL_INTLEVEL3_ANDBELOW_MASK 0x00001F4F -#define XCHAL_INTLEVEL4_ANDBELOW_MASK 0x00001F5F -#define XCHAL_INTLEVEL5_ANDBELOW_MASK 0x00001F7F -#define XCHAL_INTLEVEL6_ANDBELOW_MASK 0x00001F7F -#define XCHAL_INTLEVEL7_ANDBELOW_MASK 0x00001FFF - -/* Level of each interrupt: */ -#define XCHAL_INT0_LEVEL 1 -#define XCHAL_INT1_LEVEL 1 -#define XCHAL_INT2_LEVEL 2 -#define XCHAL_INT3_LEVEL 3 -#define XCHAL_INT4_LEVEL 4 -#define XCHAL_INT5_LEVEL 5 -#define XCHAL_INT6_LEVEL 1 -#define XCHAL_INT7_LEVEL 7 -#define XCHAL_INT8_LEVEL 1 -#define XCHAL_INT9_LEVEL 1 -#define XCHAL_INT10_LEVEL 1 -#define XCHAL_INT11_LEVEL 1 -#define XCHAL_INT12_LEVEL 1 -#define XCHAL_DEBUGLEVEL 6 /* debug interrupt level */ -#define XCHAL_HAVE_DEBUG_EXTERN_INT 1 /* OCD external db interrupt */ -#define XCHAL_NMILEVEL 7 /* NMI "level" (for use with - EXCSAVE/EPS/EPC_n, RFI n) */ - -/* Type of each interrupt: */ -#define XCHAL_INT0_TYPE XTHAL_INTTYPE_TIMER -#define XCHAL_INT1_TYPE XTHAL_INTTYPE_SOFTWARE -#define XCHAL_INT2_TYPE XTHAL_INTTYPE_WRITE_ERROR -#define XCHAL_INT3_TYPE XTHAL_INTTYPE_EXTERN_LEVEL -#define XCHAL_INT4_TYPE XTHAL_INTTYPE_EXTERN_LEVEL -#define XCHAL_INT5_TYPE XTHAL_INTTYPE_EXTERN_LEVEL -#define XCHAL_INT6_TYPE XTHAL_INTTYPE_TIMER -#define XCHAL_INT7_TYPE XTHAL_INTTYPE_NMI -#define XCHAL_INT8_TYPE XTHAL_INTTYPE_EXTERN_LEVEL -#define XCHAL_INT9_TYPE XTHAL_INTTYPE_IDMA_DONE -#define XCHAL_INT10_TYPE XTHAL_INTTYPE_IDMA_ERR -#define XCHAL_INT11_TYPE XTHAL_INTTYPE_EXTERN_LEVEL -#define XCHAL_INT12_TYPE XTHAL_INTTYPE_EXTERN_LEVEL - -/* Masks of interrupts for each type of interrupt: */ -#define XCHAL_INTTYPE_MASK_UNCONFIGURED 0xFFFFE000 -#define XCHAL_INTTYPE_MASK_EXTERN_LEVEL 0x00001938 -#define XCHAL_INTTYPE_MASK_EXTERN_EDGE 0x00000000 -#define XCHAL_INTTYPE_MASK_NMI 0x00000080 -#define XCHAL_INTTYPE_MASK_SOFTWARE 0x00000002 -#define XCHAL_INTTYPE_MASK_TIMER 0x00000041 -#define XCHAL_INTTYPE_MASK_ETIE 0x00000000 -#define XCHAL_INTTYPE_MASK_WRITE_ERROR 0x00000004 -#define XCHAL_INTTYPE_MASK_DBG_REQUEST 0x00000000 -#define XCHAL_INTTYPE_MASK_BREAKIN 0x00000000 -#define XCHAL_INTTYPE_MASK_TRAX 0x00000000 -#define XCHAL_INTTYPE_MASK_PROFILING 0x00000000 -#define XCHAL_INTTYPE_MASK_IDMA_DONE 0x00000200 -#define XCHAL_INTTYPE_MASK_IDMA_ERR 0x00000400 -#define XCHAL_INTTYPE_MASK_GS_ERR 0x00000000 -#define XCHAL_INTTYPE_MASK_L2_ERR 0x00000000 -#define XCHAL_INTTYPE_MASK_L2_STATUS 0x00000000 -#define XCHAL_INTTYPE_MASK_COR_ECC_ERR 0x00000000 -#define XCHAL_INTTYPE_MASK_WWDT 0x00000000 -#define XCHAL_INTTYPE_MASK_FXLK 0x00000000 - -/* Interrupt numbers assigned to specific interrupt sources: */ -#define XCHAL_TIMER0_INTERRUPT 0 /* CCOMPARE0 */ -#define XCHAL_TIMER1_INTERRUPT 6 /* CCOMPARE1 */ -#define XCHAL_TIMER2_INTERRUPT XTHAL_TIMER_UNCONFIGURED -#define XCHAL_TIMER3_INTERRUPT XTHAL_TIMER_UNCONFIGURED -#define XCHAL_NMI_INTERRUPT 7 /* non-maskable interrupt */ -#define XCHAL_WRITE_ERROR_INTERRUPT 2 -#define XCHAL_IDMA_DONE_INTERRUPT 9 -#define XCHAL_IDMA_ERR_INTERRUPT 10 - -/* Interrupt numbers for levels at which only one interrupt is configured: */ -#define XCHAL_INTLEVEL2_NUM 2 -#define XCHAL_INTLEVEL3_NUM 3 -#define XCHAL_INTLEVEL4_NUM 4 -#define XCHAL_INTLEVEL5_NUM 5 -#define XCHAL_INTLEVEL7_NUM 7 -/* (There are many interrupts each at level(s) 1.) */ - - -/* - * External interrupt mapping. - * These macros describe how Xtensa processor interrupt numbers - * (as numbered internally, eg. in INTERRUPT and INTENABLE registers) - * map to external BInterrupt pins, for those interrupts - * configured as external (level-triggered, edge-triggered, or NMI). - * See the Xtensa processor databook for more details. - */ - -/* Core interrupt numbers mapped to each EXTERNAL BInterrupt pin number: */ -#define XCHAL_EXTINT0_NUM 3 /* (intlevel 3) */ -#define XCHAL_EXTINT1_NUM 4 /* (intlevel 4) */ -#define XCHAL_EXTINT2_NUM 5 /* (intlevel 5) */ -#define XCHAL_EXTINT3_NUM 7 /* (intlevel 7) */ -#define XCHAL_EXTINT4_NUM 8 /* (intlevel 1) */ -#define XCHAL_EXTINT5_NUM 11 /* (intlevel 1) */ -#define XCHAL_EXTINT6_NUM 12 /* (intlevel 1) */ -/* EXTERNAL BInterrupt pin numbers mapped to each core interrupt number: */ -#define XCHAL_INT3_EXTNUM 0 /* (intlevel 3) */ -#define XCHAL_INT4_EXTNUM 1 /* (intlevel 4) */ -#define XCHAL_INT5_EXTNUM 2 /* (intlevel 5) */ -#define XCHAL_INT7_EXTNUM 3 /* (intlevel 7) */ -#define XCHAL_INT8_EXTNUM 4 /* (intlevel 1) */ -#define XCHAL_INT11_EXTNUM 5 /* (intlevel 1) */ -#define XCHAL_INT12_EXTNUM 6 /* (intlevel 1) */ - -#define XCHAL_HAVE_ISB 0 /* No ISB */ -#define XCHAL_ISB_VADDR 0 /* N/A */ -#define XCHAL_HAVE_ITB 0 /* No ITB */ -#define XCHAL_ITB_VADDR 0 /* N/A */ - -#define XCHAL_HAVE_KSL 0 /* Kernel Stack Limit */ -#define XCHAL_HAVE_ISL 0 /* Interrupt Stack Limit */ -#define XCHAL_HAVE_PSL 0 /* Pageable Stack Limit */ - - -/*---------------------------------------------------------------------- - EXCEPTIONS and VECTORS - ----------------------------------------------------------------------*/ - -#define XCHAL_XEA_VERSION 2 /* Xtensa Exception Architecture - number: 1 == XEA1 (until T1050) - 2 == XEA2 (LX) - 3 == XEA3 (NX) - 0 == XEA5 (RNX) */ -#define XCHAL_HAVE_XEA1 0 /* Exception Architecture 1 */ -#define XCHAL_HAVE_XEA2 1 /* Exception Architecture 2 */ -#define XCHAL_HAVE_XEA3 0 /* Exception Architecture 3 */ -#define XCHAL_HAVE_XEA5 0 /* Exception Architecture 5 */ -#define XCHAL_HAVE_EXCEPTIONS 1 /* exception option */ -#define XCHAL_HAVE_IMPRECISE_EXCEPTIONS 0 /* imprecise exception option */ -#define XCHAL_EXCCAUSE_NUM 64 /* Number of exceptions */ -#define XCHAL_HAVE_HALT 0 /* halt architecture option */ -#define XCHAL_HAVE_BOOTLOADER 0 /* boot loader (for TX) */ -#define XCHAL_HAVE_MEM_ECC_PARITY 0 /* local memory ECC/parity */ -#define XCHAL_HAVE_VECTOR_SELECT 0 /* relocatable vectors */ -#define XCHAL_HAVE_VECBASE 0 /* relocatable vectors */ - -#define XCHAL_RESET_VECOFS 0x00000000 -#define XCHAL_RESET_VECTOR_VADDR 0x7F000000 -#define XCHAL_RESET_VECTOR_PADDR 0x7F000000 -#define XCHAL_USER_VECOFS 0x00000000 -#define XCHAL_USER_VECTOR_VADDR 0x7F000740 -#define XCHAL_USER_VECTOR_PADDR 0x7F000740 -#define XCHAL_KERNEL_VECOFS 0x00000000 -#define XCHAL_KERNEL_VECTOR_VADDR 0x7F000700 -#define XCHAL_KERNEL_VECTOR_PADDR 0x7F000700 -#define XCHAL_DOUBLEEXC_VECOFS 0x00000000 -#define XCHAL_DOUBLEEXC_VECTOR_VADDR 0x7F0007C0 -#define XCHAL_DOUBLEEXC_VECTOR_PADDR 0x7F0007C0 -#define XCHAL_WINDOW_OF4_VECOFS 0x00000000 -#define XCHAL_WINDOW_UF4_VECOFS 0x00000040 -#define XCHAL_WINDOW_OF8_VECOFS 0x00000080 -#define XCHAL_WINDOW_UF8_VECOFS 0x000000C0 -#define XCHAL_WINDOW_OF12_VECOFS 0x00000100 -#define XCHAL_WINDOW_UF12_VECOFS 0x00000140 -#define XCHAL_WINDOW_VECTORS_VADDR 0x7F000400 -#define XCHAL_WINDOW_VECTORS_PADDR 0x7F000400 -#define XCHAL_INTLEVEL2_VECOFS 0x00000000 -#define XCHAL_INTLEVEL2_VECTOR_VADDR 0x7F000580 -#define XCHAL_INTLEVEL2_VECTOR_PADDR 0x7F000580 -#define XCHAL_INTLEVEL3_VECOFS 0x00000000 -#define XCHAL_INTLEVEL3_VECTOR_VADDR 0x7F0005C0 -#define XCHAL_INTLEVEL3_VECTOR_PADDR 0x7F0005C0 -#define XCHAL_INTLEVEL4_VECOFS 0x00000000 -#define XCHAL_INTLEVEL4_VECTOR_VADDR 0x7F000600 -#define XCHAL_INTLEVEL4_VECTOR_PADDR 0x7F000600 -#define XCHAL_INTLEVEL5_VECOFS 0x00000000 -#define XCHAL_INTLEVEL5_VECTOR_VADDR 0x7F000640 -#define XCHAL_INTLEVEL5_VECTOR_PADDR 0x7F000640 -#define XCHAL_INTLEVEL6_VECOFS 0x00000000 -#define XCHAL_INTLEVEL6_VECTOR_VADDR 0x7F000680 -#define XCHAL_INTLEVEL6_VECTOR_PADDR 0x7F000680 -#define XCHAL_DEBUG_VECOFS XCHAL_INTLEVEL6_VECOFS -#define XCHAL_DEBUG_VECTOR_VADDR XCHAL_INTLEVEL6_VECTOR_VADDR -#define XCHAL_DEBUG_VECTOR_PADDR XCHAL_INTLEVEL6_VECTOR_PADDR -#define XCHAL_NMI_VECOFS 0x00000000 -#define XCHAL_NMI_VECTOR_VADDR 0x7F0006C0 -#define XCHAL_NMI_VECTOR_PADDR 0x7F0006C0 -#define XCHAL_INTLEVEL7_VECOFS XCHAL_NMI_VECOFS -#define XCHAL_INTLEVEL7_VECTOR_VADDR XCHAL_NMI_VECTOR_VADDR -#define XCHAL_INTLEVEL7_VECTOR_PADDR XCHAL_NMI_VECTOR_PADDR - - -/*---------------------------------------------------------------------- - DEBUG MODULE - ----------------------------------------------------------------------*/ - -/* Misc */ -#define XCHAL_HAVE_DEBUG_ERI 1 /* ERI to debug module */ -#define XCHAL_HAVE_DEBUG_APB 1 /* APB to debug module */ -#define XCHAL_HAVE_DEBUG_JTAG 1 /* JTAG to debug module */ - -/* On-Chip Debug (OCD) */ -#define XCHAL_HAVE_OCD 1 /* OnChipDebug option */ -#define XCHAL_NUM_IBREAK 2 /* number of IBREAKn regs */ -#define XCHAL_NUM_DBREAK 2 /* number of DBREAKn regs */ -#define XCHAL_HAVE_OCD_DIR_ARRAY 0 /* faster OCD option (to LX4) */ -#define XCHAL_HAVE_OCD_LS32DDR 1 /* L32DDR/S32DDR (faster OCD) */ - -/* TRAX (in core) */ -#define XCHAL_HAVE_TRAX 1 /* TRAX in debug module */ -#define XCHAL_TRAX_MEM_SIZE 4096 /* TRAX memory size in bytes */ -#define XCHAL_TRAX_MEM_SHAREABLE 0 /* start/end regs; ready sig. */ -#define XCHAL_TRAX_ATB_WIDTH 0 /* ATB width (bits), 0=no ATB */ -#define XCHAL_TRAX_TIME_WIDTH 0 /* timestamp bitwidth, 0=none */ - -/* Perf counters */ -#define XCHAL_NUM_PERF_COUNTERS 0 /* performance counters */ - - -/*---------------------------------------------------------------------- - MMU - ----------------------------------------------------------------------*/ - -/* See core-matmap.h header file for more details. */ - -#define XCHAL_HAVE_TLBS 0 /* inverse of HAVE_CACHEATTR */ -#define XCHAL_HAVE_SPANNING_WAY 0 /* one way maps I+D 4GB vaddr */ -#define XCHAL_HAVE_IDENTITY_MAP 1 /* vaddr == paddr always */ -#define XCHAL_HAVE_CACHEATTR 0 /* CACHEATTR register present */ -#define XCHAL_HAVE_MIMIC_CACHEATTR 0 /* region protection */ -#define XCHAL_HAVE_XLT_CACHEATTR 0 /* region prot. w/translation */ -#define XCHAL_HAVE_PTP_MMU 0 /* full MMU (with page table - [autorefill] and protection) - usable for an MMU-based OS */ - -/* If none of the above last 5 are set, it's a custom TLB configuration. */ - -#define XCHAL_MMU_ASID_BITS 0 /* number of bits in ASIDs */ -#define XCHAL_MMU_RINGS 1 /* number of rings (1..4) */ -#define XCHAL_MMU_RING_BITS 0 /* num of bits in RING field */ - -/*---------------------------------------------------------------------- - MPU - ----------------------------------------------------------------------*/ -#define XCHAL_HAVE_MPU 1 -#define XCHAL_MPU_ENTRIES 32 -#define XCHAL_MPU_LOCK 0 - -#define XCHAL_MPU_ALIGN_REQ 1 /* MPU requires alignment of entries to background map */ -#define XCHAL_MPU_BACKGROUND_ENTRIES 2 /* number of entries in bg map*/ -#define XCHAL_MPU_BG_CACHEADRDIS 0xFF /* default CACHEADRDIS for bg */ - -#define XCHAL_MPU_ALIGN_BITS 12 -#define XCHAL_MPU_ALIGN 4096 - -/*----------------------------------------------------------------------- - CSR Parity -------------------------------------------------------------------------*/ -#define XCHAL_HAVE_CSR_PARITY 0 - - -/*---------------------------------------------------------------------- - FLEX-LOCK -------------------------------------------------------------------------*/ - -#define XCHAL_HAVE_FXLK 0 - -/*---------------------------------------------------------------------- - WWDT (Windowed Watchdog Timer) -------------------------------------------------------------------------*/ -#define XCHAL_HAVE_WWDT 0 -#endif /* !XTENSA_HAL_NON_PRIVILEGED_ONLY */ - - -#endif /* XTENSA_CORE_CONFIGURATION_H_ */ - diff --git a/src/platform/amd/acp_7_x/include/arch/xtensa/config/core-matmap.h b/src/platform/amd/acp_7_x/include/arch/xtensa/config/core-matmap.h deleted file mode 100644 index 23dca086d8b8..000000000000 --- a/src/platform/amd/acp_7_x/include/arch/xtensa/config/core-matmap.h +++ /dev/null @@ -1,106 +0,0 @@ -/* - * xtensa/config/core-matmap.h -- Memory access and translation mapping - * parameters (CHAL) of the Xtensa processor core configuration. - * - * If you are using Xtensa Tools, see (which includes - * this file) for more details. - * - * In the Xtensa processor products released to date, all parameters - * defined in this file are derivable (at least in theory) from - * information contained in the core-isa.h header file. - * In particular, the following core configuration parameters are relevant: - * XCHAL_HAVE_CACHEATTR - * XCHAL_HAVE_MIMIC_CACHEATTR - * XCHAL_HAVE_XLT_CACHEATTR - * XCHAL_HAVE_PTP_MMU - * XCHAL_ITLB_ARF_ENTRIES_LOG2 - * XCHAL_DTLB_ARF_ENTRIES_LOG2 - * XCHAL_DCACHE_IS_WRITEBACK - * XCHAL_ICACHE_SIZE (presence of I-cache) - * XCHAL_DCACHE_SIZE (presence of D-cache) - * XCHAL_HW_VERSION_MAJOR - * XCHAL_HW_VERSION_MINOR - */ - -/* Customer ID=20511; Build=0xb6494; Copyright (c) 1999-2024 Tensilica Inc. - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - - -#ifndef XTENSA_CONFIG_CORE_MATMAP_H -#define XTENSA_CONFIG_CORE_MATMAP_H - - -/*---------------------------------------------------------------------- - CACHE (MEMORY ACCESS) ATTRIBUTES - ----------------------------------------------------------------------*/ -/*---------------------------------------------------------------------- - MPU - ----------------------------------------------------------------------*/ - -/* Mappings for legacy constants where appropriate */ - -#define XCHAL_CA_WRITEBACK (XTHAL_MEM_WRITEBACK | XTHAL_AR_RWXrwx) - -#define XCHAL_CA_WRITEBACK_NOALLOC (XTHAL_MEM_WRITEBACK_NOALLOC| XTHAL_AR_RWXrwx ) - -#define XCHAL_CA_WRITETHRU (XTHAL_MEM_WRITETHRU | XTHAL_AR_RWXrwx) - -#define XCHAL_CA_ILLEGAL (XTHAL_AR_NONE | XTHAL_MEM_DEVICE) -#define XCHAL_CA_BYPASS (XTHAL_AR_RWXrwx | XTHAL_MEM_DEVICE) -#define XCHAL_CA_BYPASSBUF (XTHAL_AR_RWXrwx | XTHAL_MEM_DEVICE |\ - XTHAL_MEM_BUFFERABLE) -#define XCHAL_CA_BYPASS_RX (XTHAL_AR_RX | XTHAL_MEM_DEVICE) -#define XCHAL_CA_BYPASS_RW (XTHAL_AR_RW | XTHAL_MEM_DEVICE) -#define XCHAL_CA_BYPASS_R (XTHAL_AR_R | XTHAL_MEM_DEVICE) -#define XCHAL_HAVE_CA_WRITEBACK_NOALLOC 1 - -#define XCHAL_CA_R (XTHAL_AR_R) -#define XCHAL_CA_RX (XTHAL_AR_RX) -#define XCHAL_CA_RW (XTHAL_AR_RW) -#define XCHAL_CA_RWX (XTHAL_AR_RWX) - - -/* - * Contents of MPU background map. - * NOTE: caller must define the XCHAL_MPU_BGMAP() macro (not defined here - * but specified below) before expanding the XCHAL_MPU_BACKGROUND_MAP(s) macro. - * - * XCHAL_MPU_BGMAP(s, vaddr_start, vaddr_last, rights, memtype, x...) - * - * s = passed from XCHAL_MPU_BACKGROUND_MAP(s), eg. to select how to expand - * vaddr_start = first byte of region (always 0 for first entry) - * vaddr_end = last byte of region (always 0xFFFFFFFF for last entry) - * rights = access rights - * memtype = memory type - * x = reserved for future use (0 until then) - */ -/* parasoft-begin-suppress MISRA2012-RULE-20_7 "Macro use model requires s to not be in ()" */ -#define XCHAL_MPU_BACKGROUND_MAP(s) \ - XCHAL_MPU_BGMAP(s, 0x00000000, 0x7fffffff, 7, 6, 0) \ - XCHAL_MPU_BGMAP(s, 0x80000000, 0xffffffff, 7, 6, 0) \ -/* parasoft-end-suppress MISRA2012-RULE-20_7 "Macro use model requires s to not be in ()" */ - - /* end */ - - - -#endif /*XTENSA_CONFIG_CORE_MATMAP_H*/ - diff --git a/src/platform/amd/acp_7_x/include/arch/xtensa/config/defs.h b/src/platform/amd/acp_7_x/include/arch/xtensa/config/defs.h deleted file mode 100644 index cbcb6b5c39de..000000000000 --- a/src/platform/amd/acp_7_x/include/arch/xtensa/config/defs.h +++ /dev/null @@ -1,38 +0,0 @@ -/* Definitions for Xtensa instructions, types, and protos. */ - -/* Customer ID=20511; Build=0xb6494; Copyright (c) 2003-2004 Tensilica Inc. - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - -/* NOTE: This file exists only for backward compatibility with T1050 - and earlier Xtensa releases. It includes only a subset of the - available header files. */ - -#ifndef _XTENSA_BASE_HEADER -#define _XTENSA_BASE_HEADER - -#ifdef __XTENSA__ - -#include -#include -#include - -#endif /* __XTENSA__ */ -#endif /* !_XTENSA_BASE_HEADER */ diff --git a/src/platform/amd/acp_7_x/include/arch/xtensa/config/specreg.h b/src/platform/amd/acp_7_x/include/arch/xtensa/config/specreg.h deleted file mode 100644 index 81a0a6780016..000000000000 --- a/src/platform/amd/acp_7_x/include/arch/xtensa/config/specreg.h +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Xtensa Special Register symbolic names - */ - -/* $Id: //depot/rel/Homewood/ib.9/Xtensa/SWConfig/hal/specreg.h.tpp#1 $ */ - -/* Customer ID=20511; Build=0xb6494; Copyright (c) 1998-2002 Tensilica Inc. - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - -#ifndef XTENSA_SPECREG_H -#define XTENSA_SPECREG_H - -/* Include these special register bitfield definitions, for historical reasons: */ -#include - - -/* Special registers: */ -#define LBEG 0 -#define LEND 1 -#define LCOUNT 2 -#define SAR 3 -#define BR 4 -#define PREFCTL 40 -#define WINDOWBASE 72 -#define WINDOWSTART 73 -#define MPUENB 90 -#define ERACCESS 95 -#define IBREAKENABLE 96 -#define MEMCTL 97 -#define CACHEADRDIS 98 -#define DDR 104 -#define IBREAKA_0 128 -#define IBREAKA_1 129 -#define DBREAKA_0 144 -#define DBREAKA_1 145 -#define DBREAKC_0 160 -#define DBREAKC_1 161 -#define EPC_1 177 -#define EPC_2 178 -#define EPC_3 179 -#define EPC_4 180 -#define EPC_5 181 -#define EPC_6 182 -#define EPC_7 183 -#define DEPC 192 -#define EPS_2 194 -#define EPS_3 195 -#define EPS_4 196 -#define EPS_5 197 -#define EPS_6 198 -#define EPS_7 199 -#define EXCSAVE_1 209 -#define EXCSAVE_2 210 -#define EXCSAVE_3 211 -#define EXCSAVE_4 212 -#define EXCSAVE_5 213 -#define EXCSAVE_6 214 -#define EXCSAVE_7 215 -#define CPENABLE 224 -#define INTERRUPT 226 -#define INTENABLE 228 -#define PS 230 -#define EXCCAUSE 232 -#define DEBUGCAUSE 233 -#define CCOUNT 234 -#define PRID 235 -#define ICOUNT 236 -#define ICOUNTLEVEL 237 -#define EXCVADDR 238 -#define CCOMPARE_0 240 -#define CCOMPARE_1 241 -#define MISC_REG_0 244 -#define MISC_REG_1 245 - - -/* Special cases (bases of special register series): */ -#define IBREAKA 128 -#define DBREAKA 144 -#define DBREAKC 160 -#define EPC 176 -#define EPS 192 -#define EXCSAVE 208 -#define CCOMPARE 240 - -/* Special names for read-only and write-only interrupt registers: */ -#define INTREAD 226 -#define INTSET 226 -#define INTCLEAR 227 - -#endif /* XTENSA_SPECREG_H */ - diff --git a/src/platform/amd/acp_7_x/include/arch/xtensa/config/system.h b/src/platform/amd/acp_7_x/include/arch/xtensa/config/system.h deleted file mode 100644 index 5a03eebf7b85..000000000000 --- a/src/platform/amd/acp_7_x/include/arch/xtensa/config/system.h +++ /dev/null @@ -1,262 +0,0 @@ -/* - * xtensa/config/system.h -- HAL definitions that are dependent on SYSTEM configuration - * - * NOTE: The location and contents of this file are highly subject to change. - * - * Source for configuration-independent binaries (which link in a - * configuration-specific HAL library) must NEVER include this file. - * The HAL itself has historically included this file in some instances, - * but this is not appropriate either, because the HAL is meant to be - * core-specific but system independent. - */ - -/* Customer ID=20511; Build=0xb6494; Copyright (c) 2000-2010 Tensilica Inc. - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - - -#ifndef XTENSA_CONFIG_SYSTEM_H -#define XTENSA_CONFIG_SYSTEM_H - - -/*---------------------------------------------------------------------- - CONFIGURED SOFTWARE OPTIONS - ----------------------------------------------------------------------*/ - -#define XSHAL_USE_ABSOLUTE_LITERALS 0 /* (sw-only option, whether software uses absolute literals) */ -#define XSHAL_HAVE_TEXT_SECTION_LITERALS 1 /* Set if there is some memory that allows both code and literals. */ - -#define XSHAL_ABI XTHAL_ABI_WINDOWED /* (sw-only option, selected ABI) */ -/* The above maps to one of the following constants: */ -#define XTHAL_ABI_WINDOWED 0 -#define XTHAL_ABI_CALL0 1 - -#define XSHAL_CLIB XTHAL_CLIB_XCLIB /* (sw-only option, selected C library) */ -/* The above maps to one of the following constants: */ -#define XTHAL_CLIB_NEWLIB 0 -#define XTHAL_CLIB_UCLIBC 1 -#define XTHAL_CLIB_XCLIB 2 - -#define XSHAL_USE_FLOATING_POINT 1 - -#define XSHAL_FLOATING_POINT_ABI 1 - -/* SW workarounds enabled for HW errata: */ - -/*---------------------------------------------------------------------- - DEVICE ADDRESSES - ----------------------------------------------------------------------*/ - -/* - * Strange place to find these, but the configuration GUI - * allows moving these around to account for various core - * configurations. Specific boards (and their BSP software) - * will have specific meanings for these components. - */ - -/* I/O Block areas: */ -#define XSHAL_IOBLOCK_CACHED_VADDR 0x70000000 -#define XSHAL_IOBLOCK_CACHED_PADDR 0x70000000 -#define XSHAL_IOBLOCK_CACHED_SIZE 0x0E000000 - -#define XSHAL_IOBLOCK_BYPASS_VADDR 0x90000000 -#define XSHAL_IOBLOCK_BYPASS_PADDR 0x90000000 -#define XSHAL_IOBLOCK_BYPASS_SIZE 0x0E000000 - -/* System ROM: */ -#define XSHAL_ROM_VADDR 0x50000000 -#define XSHAL_ROM_PADDR 0x50000000 -#define XSHAL_ROM_SIZE 0x00020000 -/* Largest available area (free of vectors): */ -#define XSHAL_ROM_AVAIL_VADDR 0x50000000 -#define XSHAL_ROM_AVAIL_VSIZE 0x00020000 - -/* System RAM: */ -#define XSHAL_RAM_VADDR 0x60000000 -#define XSHAL_RAM_PADDR 0x60000000 -#define XSHAL_RAM_VSIZE 0x1F000000 -#define XSHAL_RAM_PSIZE 0x1F000000 -#define XSHAL_RAM_SIZE XSHAL_RAM_PSIZE -/* Largest available area (free of vectors): */ -#define XSHAL_RAM_AVAIL_VADDR 0x60000000 -#define XSHAL_RAM_AVAIL_VSIZE 0x1F000000 - -/* - * Shadow system RAM (same device as system RAM, at different address). - * (Emulation boards need this for the SONIC Ethernet driver - * when data caches are configured for writeback mode.) - * NOTE: on full MMU configs, this points to the BYPASS virtual address - * of system RAM, ie. is the same as XSHAL_RAM_* except that virtual - * addresses are viewed through the BYPASS static map rather than - * the CACHED static map. - */ -#define XSHAL_RAM_BYPASS_VADDR 0x20000000 -#define XSHAL_RAM_BYPASS_PADDR 0x20000000 -#define XSHAL_RAM_BYPASS_PSIZE 0x1F000000 - -/* Alternate system RAM (different device than system RAM): */ - -/* Some available location in which to place devices in a simulation (eg. XTMP): */ -#define XSHAL_SIMIO_CACHED_VADDR 0xC0000000 -#define XSHAL_SIMIO_BYPASS_VADDR 0xC0000000 -#define XSHAL_SIMIO_PADDR 0xC0000000 -#define XSHAL_SIMIO_SIZE 0x20000000 - - -/*---------------------------------------------------------------------- - * For use by reference testbench exit and diagnostic routines. - */ -#define XSHAL_MAGIC_EXIT 0xc61b3000 -#define XSHAL_STL_INFO_LOCATION 0xbffffffc - -/*---------------------------------------------------------------------- - * DEVICE-ADDRESS DEPENDENT... - * - * Values written to CACHEATTR special register (or its equivalent) - * to enable and disable caches in various modes. - *----------------------------------------------------------------------*/ - -/*---------------------------------------------------------------------- - BACKWARD COMPATIBILITY ... - ----------------------------------------------------------------------*/ - -/* - * NOTE: the following two macros are DEPRECATED. Use the latter - * board-specific macros instead, which are specially tuned for the - * particular target environments' memory maps. - */ -#define XSHAL_CACHEATTR_BYPASS XSHAL_XT2000_CACHEATTR_BYPASS /* disable caches in bypass mode */ -#define XSHAL_CACHEATTR_DEFAULT XSHAL_XT2000_CACHEATTR_DEFAULT /* default setting to enable caches (no writeback!) */ - -/*---------------------------------------------------------------------- - GENERIC - ----------------------------------------------------------------------*/ - -/* For the following, a 512MB region is used if it contains a system (PIF) RAM, - * system (PIF) ROM, local memory, or XLMI. */ - -/* These set any unused 512MB region to cache-BYPASS attribute: */ -#define XSHAL_ALLVALID_CACHEATTR_WRITEBACK 0x42224422 /* enable caches in write-back mode */ -#define XSHAL_ALLVALID_CACHEATTR_WRITEALLOC 0x12221122 /* enable caches in write-allocate mode */ -#define XSHAL_ALLVALID_CACHEATTR_WRITETHRU 0x12221122 /* enable caches in write-through mode */ -#define XSHAL_ALLVALID_CACHEATTR_BYPASS 0x22222222 /* disable caches in bypass mode */ -#define XSHAL_ALLVALID_CACHEATTR_DEFAULT XSHAL_ALLVALID_CACHEATTR_WRITEBACK /* default setting to enable caches */ - -/* These set any unused 512MB region to ILLEGAL attribute: */ -#define XSHAL_STRICT_CACHEATTR_WRITEBACK 0x4FFF44FF /* enable caches in write-back mode */ -#define XSHAL_STRICT_CACHEATTR_WRITEALLOC 0x1FFF11FF /* enable caches in write-allocate mode */ -#define XSHAL_STRICT_CACHEATTR_WRITETHRU 0x1FFF11FF /* enable caches in write-through mode */ -#define XSHAL_STRICT_CACHEATTR_BYPASS 0x2FFF22FF /* disable caches in bypass mode */ -#define XSHAL_STRICT_CACHEATTR_DEFAULT XSHAL_STRICT_CACHEATTR_WRITEBACK /* default setting to enable caches */ - -/* These set the first 512MB, if unused, to ILLEGAL attribute to help catch - * NULL-pointer dereference bugs; all other unused 512MB regions are set - * to cache-BYPASS attribute: */ -#define XSHAL_TRAPNULL_CACHEATTR_WRITEBACK 0x4222442F /* enable caches in write-back mode */ -#define XSHAL_TRAPNULL_CACHEATTR_WRITEALLOC 0x1222112F /* enable caches in write-allocate mode */ -#define XSHAL_TRAPNULL_CACHEATTR_WRITETHRU 0x1222112F /* enable caches in write-through mode */ -#define XSHAL_TRAPNULL_CACHEATTR_BYPASS 0x2222222F /* disable caches in bypass mode */ -#define XSHAL_TRAPNULL_CACHEATTR_DEFAULT XSHAL_TRAPNULL_CACHEATTR_WRITEBACK /* default setting to enable caches */ - -/*---------------------------------------------------------------------- - ISS (Instruction Set Simulator) SPECIFIC ... - ----------------------------------------------------------------------*/ - -/* For now, ISS defaults to the TRAPNULL settings: */ -#define XSHAL_ISS_CACHEATTR_WRITEBACK XSHAL_TRAPNULL_CACHEATTR_WRITEBACK -#define XSHAL_ISS_CACHEATTR_WRITEALLOC XSHAL_TRAPNULL_CACHEATTR_WRITEALLOC -#define XSHAL_ISS_CACHEATTR_WRITETHRU XSHAL_TRAPNULL_CACHEATTR_WRITETHRU -#define XSHAL_ISS_CACHEATTR_BYPASS XSHAL_TRAPNULL_CACHEATTR_BYPASS -#define XSHAL_ISS_CACHEATTR_DEFAULT XSHAL_TRAPNULL_CACHEATTR_WRITEBACK - -#define XSHAL_ISS_PIPE_REGIONS 0 -#define XSHAL_ISS_SDRAM_REGIONS 0 - - -/*---------------------------------------------------------------------- - XT2000 BOARD SPECIFIC ... - ----------------------------------------------------------------------*/ - -/* For the following, a 512MB region is used if it contains any system RAM, - * system ROM, local memory, XLMI, or other XT2000 board device or memory. - * Regions containing devices are forced to cache-BYPASS mode regardless - * of whether the macro is _WRITEBACK vs. _BYPASS etc. */ - -/* These set any 512MB region unused on the XT2000 to ILLEGAL attribute: */ -#define XSHAL_XT2000_CACHEATTR_WRITEBACK 0x4FF2442F /* enable caches in write-back mode */ -#define XSHAL_XT2000_CACHEATTR_WRITEALLOC 0x1FF2112F /* enable caches in write-allocate mode */ -#define XSHAL_XT2000_CACHEATTR_WRITETHRU 0x1FF2112F /* enable caches in write-through mode */ -#define XSHAL_XT2000_CACHEATTR_BYPASS 0x2FF2222F /* disable caches in bypass mode */ -#define XSHAL_XT2000_CACHEATTR_DEFAULT XSHAL_XT2000_CACHEATTR_WRITEBACK /* default setting to enable caches */ - -#define XSHAL_XT2000_PIPE_REGIONS 0x00000000 /* BusInt pipeline regions */ -#define XSHAL_XT2000_SDRAM_REGIONS 0x00000044 /* BusInt SDRAM regions */ - - -/*---------------------------------------------------------------------- - VECTOR INFO AND SIZES - ----------------------------------------------------------------------*/ - -#define XSHAL_VECTORS_PACKED 0 /* UNUSED */ -#define XSHAL_STATIC_VECTOR_SELECT 0 -#define XSHAL_RESET_VECTOR_VADDR 0x7F000000 -#define XSHAL_RESET_VECTOR_PADDR 0x7F000000 - -/* - * Sizes allocated to vectors by the system (memory map) configuration. - * These sizes are constrained by core configuration (eg. one vector's - * code cannot overflow into another vector) but are dependent on the - * system or board (or LSP) memory map configuration. - * - * Whether or not each vector happens to be in a system ROM is also - * a system configuration matter, sometimes useful, included here also: - */ -#define XSHAL_RESET_VECTOR_SIZE 0x00000300 -#define XSHAL_RESET_VECTOR_ISROM 0 -#define XSHAL_USER_VECTOR_SIZE 0x00000038 -#define XSHAL_USER_VECTOR_ISROM 0 -#define XSHAL_PROGRAMEXC_VECTOR_SIZE XSHAL_USER_VECTOR_SIZE /* for backward compatibility */ -#define XSHAL_USEREXC_VECTOR_SIZE XSHAL_USER_VECTOR_SIZE /* for backward compatibility */ -#define XSHAL_KERNEL_VECTOR_SIZE 0x00000038 -#define XSHAL_KERNEL_VECTOR_ISROM 0 -#define XSHAL_STACKEDEXC_VECTOR_SIZE XSHAL_KERNEL_VECTOR_SIZE /* for backward compatibility */ -#define XSHAL_KERNELEXC_VECTOR_SIZE XSHAL_KERNEL_VECTOR_SIZE /* for backward compatibility */ -#define XSHAL_DOUBLEEXC_VECTOR_SIZE 0x00000040 -#define XSHAL_DOUBLEEXC_VECTOR_ISROM 0 -#define XSHAL_WINDOW_VECTORS_SIZE 0x00000178 -#define XSHAL_WINDOW_VECTORS_ISROM 0 -#define XSHAL_INTLEVEL2_VECTOR_SIZE 0x00000038 -#define XSHAL_INTLEVEL2_VECTOR_ISROM 0 -#define XSHAL_INTLEVEL3_VECTOR_SIZE 0x00000038 -#define XSHAL_INTLEVEL3_VECTOR_ISROM 0 -#define XSHAL_INTLEVEL4_VECTOR_SIZE 0x00000038 -#define XSHAL_INTLEVEL4_VECTOR_ISROM 0 -#define XSHAL_INTLEVEL5_VECTOR_SIZE 0x00000038 -#define XSHAL_INTLEVEL5_VECTOR_ISROM 0 -#define XSHAL_INTLEVEL6_VECTOR_SIZE 0x00000038 -#define XSHAL_INTLEVEL6_VECTOR_ISROM 0 -#define XSHAL_DEBUG_VECTOR_SIZE XSHAL_INTLEVEL6_VECTOR_SIZE -#define XSHAL_DEBUG_VECTOR_ISROM XSHAL_INTLEVEL6_VECTOR_ISROM -#define XSHAL_NMI_VECTOR_SIZE 0x00000038 -#define XSHAL_NMI_VECTOR_ISROM 0 -#define XSHAL_INTLEVEL7_VECTOR_SIZE XSHAL_NMI_VECTOR_SIZE - -#endif /*XTENSA_CONFIG_SYSTEM_H*/ - diff --git a/src/platform/amd/acp_7_x/include/arch/xtensa/config/tie-asm.h b/src/platform/amd/acp_7_x/include/arch/xtensa/config/tie-asm.h deleted file mode 100644 index 18935b08cc73..000000000000 --- a/src/platform/amd/acp_7_x/include/arch/xtensa/config/tie-asm.h +++ /dev/null @@ -1,382 +0,0 @@ -/* - * tie-asm.h -- compile-time HAL assembler definitions dependent on CORE & TIE - * - * NOTE: This header file is not meant to be included directly. - */ - -/* This header file contains assembly-language definitions (assembly - macros, etc.) for this specific Xtensa processor's TIE extensions - and options. It is customized to this Xtensa processor configuration. - - Customer ID=20511; Build=0xb6494; Copyright (c) 1999-2024 Cadence Design Systems Inc. - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - -#ifndef _XTENSA_CORE_TIE_ASM_H -#define _XTENSA_CORE_TIE_ASM_H - -#include - -/* Selection parameter values for save-area save/restore macros: */ -/* Option vs. TIE: */ -#define XTHAL_SAS_TIE 0x0001 /* custom extension or coprocessor */ -#define XTHAL_SAS_OPT 0x0002 /* optional (and not a coprocessor) */ -#define XTHAL_SAS_ANYOT 0x0003 /* both of the above */ -/* Whether used automatically by compiler: */ -#define XTHAL_SAS_NOCC 0x0004 /* not used by compiler w/o special opts/code */ -#define XTHAL_SAS_CC 0x0008 /* used by compiler without special opts/code */ -#define XTHAL_SAS_ANYCC 0x000C /* both of the above */ -/* ABI handling across function calls: */ -#define XTHAL_SAS_CALR 0x0010 /* caller-saved */ -#define XTHAL_SAS_CALE 0x0020 /* callee-saved */ -#define XTHAL_SAS_GLOB 0x0040 /* global across function calls (in thread) */ -#define XTHAL_SAS_ANYABI 0x0070 /* all of the above three */ -/* Misc */ -#define XTHAL_SAS_ALL 0xFFFF /* include all default NCP contents */ -#define XTHAL_SAS3(optie,ccuse,abi) ( ((optie) & XTHAL_SAS_ANYOT) \ - | ((ccuse) & XTHAL_SAS_ANYCC) \ - | ((abi) & XTHAL_SAS_ANYABI) ) - - - /* - * Macro to store all non-coprocessor (extra) custom TIE and optional state - * (not including zero-overhead loop registers). - * Required parameters: - * ptr Save area pointer address register (clobbered) - * (register must contain a 4 byte aligned address). - * at1..at4 Four temporary address registers (first XCHAL_NCP_NUM_ATMPS - * registers are clobbered, the remaining are unused). - * Optional parameters: - * continue If macro invoked as part of a larger store sequence, set to 1 - * if this is not the first in the sequence. Defaults to 0. - * ofs Offset from start of larger sequence (from value of first ptr - * in sequence) at which to store. Defaults to next available space - * (or 0 if is 0). - * select Select what category(ies) of registers to store, as a bitmask - * (see XTHAL_SAS_xxx constants). Defaults to all registers. - * alloc Select what category(ies) of registers to allocate; if any - * category is selected here that is not in , space for - * the corresponding registers is skipped without doing any load. - */ - .macro xchal_ncp_load ptr at1 at2 at3 at4 continue=0 ofs=-1 select=XTHAL_SAS_ALL alloc=0 - xchal_sa_start \continue, \ofs - // Optional global registers used by default by the compiler: - .ifeq (XTHAL_SAS_OPT | XTHAL_SAS_CC | XTHAL_SAS_GLOB) & ~(\select) - xchal_sa_align \ptr, 0, 1016, 4, 4 - l32i \at1, \ptr, .Lxchal_ofs_+0 - wur.threadptr \at1 // threadptr option - .set .Lxchal_ofs_, .Lxchal_ofs_ + 4 - .elseif ((XTHAL_SAS_OPT | XTHAL_SAS_CC | XTHAL_SAS_GLOB) & ~(\alloc)) == 0 - xchal_sa_align \ptr, 0, 1016, 4, 4 - .set .Lxchal_ofs_, .Lxchal_ofs_ + 4 - .endif - // Optional caller-saved registers not used by default by the compiler: - .ifeq (XTHAL_SAS_OPT | XTHAL_SAS_NOCC | XTHAL_SAS_CALR) & ~(\select) - xchal_sa_align \ptr, 0, 1016, 4, 4 - l32i \at1, \ptr, .Lxchal_ofs_+0 - wsr.br \at1 // boolean option - .set .Lxchal_ofs_, .Lxchal_ofs_ + 4 - .elseif ((XTHAL_SAS_OPT | XTHAL_SAS_NOCC | XTHAL_SAS_CALR) & ~(\alloc)) == 0 - xchal_sa_align \ptr, 0, 1016, 4, 4 - .set .Lxchal_ofs_, .Lxchal_ofs_ + 4 - .endif - // Custom caller-saved registers not used by default by the compiler: - .ifeq (XTHAL_SAS_TIE | XTHAL_SAS_NOCC | XTHAL_SAS_CALR) & ~(\select) - xchal_sa_align \ptr, 0, 1016, 4, 4 - l32i \at1, \ptr, .Lxchal_ofs_+0 - wur.xnne_rerr \at1 // ureg 0 - .set .Lxchal_ofs_, .Lxchal_ofs_ + 4 - .elseif ((XTHAL_SAS_TIE | XTHAL_SAS_NOCC | XTHAL_SAS_CALR) & ~(\alloc)) == 0 - xchal_sa_align \ptr, 0, 1016, 4, 4 - .set .Lxchal_ofs_, .Lxchal_ofs_ + 4 - .endif - .endm // xchal_ncp_load - - -#define XCHAL_NCP_NUM_ATMPS 1 - - /* - * Macro to store the state of TIE coprocessor AudioEngineLX. - * Required parameters: - * ptr Save area pointer address register (clobbered) - * (register must contain a 16 byte aligned address). - * at1..at4 Four temporary address registers (first XCHAL_CP1_NUM_ATMPS - * registers are clobbered, the remaining are unused). - * Optional parameters are the same as for xchal_ncp_store. - */ -#define xchal_cp_AudioEngineLX_store xchal_cp1_store - .macro xchal_cp1_store ptr at1 at2 at3 at4 continue=0 ofs=-1 select=XTHAL_SAS_ALL alloc=0 - xchal_sa_start \continue, \ofs - // Custom caller-saved registers not used by default by the compiler: - .ifeq (XTHAL_SAS_TIE | XTHAL_SAS_NOCC | XTHAL_SAS_CALR) & ~(\select) - xchal_sa_align \ptr, 0, 0, 16, 16 - ae_s64.i aed0, \ptr, .Lxchal_ofs_+56 - addi \ptr, \ptr, 64 - ae_s64.i aed1, \ptr, .Lxchal_ofs_+0 - ae_s64.i aed2, \ptr, .Lxchal_ofs_+8 - ae_s64.i aed3, \ptr, .Lxchal_ofs_+16 - ae_s64.i aed4, \ptr, .Lxchal_ofs_+24 - ae_s64.i aed5, \ptr, .Lxchal_ofs_+32 - ae_s64.i aed6, \ptr, .Lxchal_ofs_+40 - ae_s64.i aed7, \ptr, .Lxchal_ofs_+48 - ae_s64.i aed8, \ptr, .Lxchal_ofs_+56 - addi \ptr, \ptr, 64 - ae_s64.i aed9, \ptr, .Lxchal_ofs_+0 - ae_s64.i aed10, \ptr, .Lxchal_ofs_+8 - ae_s64.i aed11, \ptr, .Lxchal_ofs_+16 - ae_s64.i aed12, \ptr, .Lxchal_ofs_+24 - ae_s64.i aed13, \ptr, .Lxchal_ofs_+32 - ae_s64.i aed14, \ptr, .Lxchal_ofs_+40 - ae_s64.i aed15, \ptr, .Lxchal_ofs_+48 - ae_s64.i aed16, \ptr, .Lxchal_ofs_+56 - addi \ptr, \ptr, 64 - ae_s64.i aed17, \ptr, .Lxchal_ofs_+0 - ae_s64.i aed18, \ptr, .Lxchal_ofs_+8 - ae_s64.i aed19, \ptr, .Lxchal_ofs_+16 - ae_s64.i aed20, \ptr, .Lxchal_ofs_+24 - ae_s64.i aed21, \ptr, .Lxchal_ofs_+32 - ae_s64.i aed22, \ptr, .Lxchal_ofs_+40 - ae_s64.i aed23, \ptr, .Lxchal_ofs_+48 - ae_s64.i aed24, \ptr, .Lxchal_ofs_+56 - addi \ptr, \ptr, 64 - ae_s64.i aed25, \ptr, .Lxchal_ofs_+0 - ae_s64.i aed26, \ptr, .Lxchal_ofs_+8 - ae_s64.i aed27, \ptr, .Lxchal_ofs_+16 - ae_s64.i aed28, \ptr, .Lxchal_ofs_+24 - ae_s64.i aed29, \ptr, .Lxchal_ofs_+32 - ae_s64.i aed30, \ptr, .Lxchal_ofs_+40 - ae_s64.i aed31, \ptr, .Lxchal_ofs_+48 - ae_movae \at1, aep0 - s8i \at1, \ptr, .Lxchal_ofs_+56 - ae_movae \at1, aep1 - s8i \at1, \ptr, .Lxchal_ofs_+57 - ae_movae \at1, aep2 - s8i \at1, \ptr, .Lxchal_ofs_+58 - ae_movae \at1, aep3 - s8i \at1, \ptr, .Lxchal_ofs_+59 - addi \ptr, \ptr, 64 - ae_salign128.i u0, \ptr, .Lxchal_ofs_+0 - ae_salign128.i u1, \ptr, .Lxchal_ofs_+16 - ae_salign128.i u2, \ptr, .Lxchal_ofs_+32 - ae_salign128.i u3, \ptr, .Lxchal_ofs_+48 - addi \ptr, \ptr, -320 - ae_movdrzbvc aed0 // ureg AE_ZBIASV8C - ae_s64.i aed0, \ptr, .Lxchal_ofs_+0 + 0 - ae_movvfcrfsr aed0 // ureg FCR_FSR - ae_s64.i aed0, \ptr, .Lxchal_ofs_+8 + 0 - rur.ae_ovf_sar \at1 // ureg 240 - s32i \at1, \ptr, .Lxchal_ofs_+16 - rur.ae_bithead \at1 // ureg 241 - s32i \at1, \ptr, .Lxchal_ofs_+20 - rur.ae_ts_fts_bu_bp \at1 // ureg 242 - s32i \at1, \ptr, .Lxchal_ofs_+24 - rur.ae_cw_sd_no \at1 // ureg 243 - s32i \at1, \ptr, .Lxchal_ofs_+28 - rur.ae_cbegin0 \at1 // ureg 246 - s32i \at1, \ptr, .Lxchal_ofs_+32 - rur.ae_cend0 \at1 // ureg 247 - s32i \at1, \ptr, .Lxchal_ofs_+36 - rur.ae_cbegin1 \at1 // ureg 248 - s32i \at1, \ptr, .Lxchal_ofs_+40 - rur.ae_cend1 \at1 // ureg 249 - s32i \at1, \ptr, .Lxchal_ofs_+44 - rur.ae_cbegin2 \at1 // ureg 250 - s32i \at1, \ptr, .Lxchal_ofs_+48 - rur.ae_cend2 \at1 // ureg 251 - s32i \at1, \ptr, .Lxchal_ofs_+52 - .set .Lxchal_ofs_, .Lxchal_ofs_ + 384 - .elseif ((XTHAL_SAS_TIE | XTHAL_SAS_NOCC | XTHAL_SAS_CALR) & ~(\alloc)) == 0 - xchal_sa_align \ptr, 0, 0, 16, 16 - .set .Lxchal_ofs_, .Lxchal_ofs_ + 384 - .endif - .endm // xchal_cp1_store - - /* - * Macro to load the state of TIE coprocessor AudioEngineLX. - * Required parameters: - * ptr Save area pointer address register (clobbered) - * (register must contain a 16 byte aligned address). - * at1..at4 Four temporary address registers (first XCHAL_CP1_NUM_ATMPS - * registers are clobbered, the remaining are unused). - * Optional parameters are the same as for xchal_ncp_load. - */ -#define xchal_cp_AudioEngineLX_load xchal_cp1_load - .macro xchal_cp1_load ptr at1 at2 at3 at4 continue=0 ofs=-1 select=XTHAL_SAS_ALL alloc=0 - xchal_sa_start \continue, \ofs - // Custom caller-saved registers not used by default by the compiler: - .ifeq (XTHAL_SAS_TIE | XTHAL_SAS_NOCC | XTHAL_SAS_CALR) & ~(\select) - xchal_sa_align \ptr, 0, 0, 16, 16 - ae_l64.i aed0, \ptr, .Lxchal_ofs_+0 + 0 // ureg AE_ZBIASV8C - ae_movzbvcdr aed0 - ae_l64.i aed0, \ptr, .Lxchal_ofs_+8 + 0 // ureg FCR_FSR - ae_movfcrfsrv aed0 - l32i \at1, \ptr, .Lxchal_ofs_+16 - wur.ae_ovf_sar \at1 // ureg 240 - l32i \at1, \ptr, .Lxchal_ofs_+20 - wur.ae_bithead \at1 // ureg 241 - l32i \at1, \ptr, .Lxchal_ofs_+24 - wur.ae_ts_fts_bu_bp \at1 // ureg 242 - l32i \at1, \ptr, .Lxchal_ofs_+28 - wur.ae_cw_sd_no \at1 // ureg 243 - l32i \at1, \ptr, .Lxchal_ofs_+32 - wur.ae_cbegin0 \at1 // ureg 246 - l32i \at1, \ptr, .Lxchal_ofs_+36 - wur.ae_cend0 \at1 // ureg 247 - l32i \at1, \ptr, .Lxchal_ofs_+40 - wur.ae_cbegin1 \at1 // ureg 248 - l32i \at1, \ptr, .Lxchal_ofs_+44 - wur.ae_cend1 \at1 // ureg 249 - l32i \at1, \ptr, .Lxchal_ofs_+48 - wur.ae_cbegin2 \at1 // ureg 250 - l32i \at1, \ptr, .Lxchal_ofs_+52 - wur.ae_cend2 \at1 // ureg 251 - ae_l64.i aed0, \ptr, .Lxchal_ofs_+56 - addi \ptr, \ptr, 64 - ae_l64.i aed1, \ptr, .Lxchal_ofs_+0 - ae_l64.i aed2, \ptr, .Lxchal_ofs_+8 - ae_l64.i aed3, \ptr, .Lxchal_ofs_+16 - ae_l64.i aed4, \ptr, .Lxchal_ofs_+24 - ae_l64.i aed5, \ptr, .Lxchal_ofs_+32 - ae_l64.i aed6, \ptr, .Lxchal_ofs_+40 - ae_l64.i aed7, \ptr, .Lxchal_ofs_+48 - ae_l64.i aed8, \ptr, .Lxchal_ofs_+56 - addi \ptr, \ptr, 64 - ae_l64.i aed9, \ptr, .Lxchal_ofs_+0 - ae_l64.i aed10, \ptr, .Lxchal_ofs_+8 - ae_l64.i aed11, \ptr, .Lxchal_ofs_+16 - ae_l64.i aed12, \ptr, .Lxchal_ofs_+24 - ae_l64.i aed13, \ptr, .Lxchal_ofs_+32 - ae_l64.i aed14, \ptr, .Lxchal_ofs_+40 - ae_l64.i aed15, \ptr, .Lxchal_ofs_+48 - ae_l64.i aed16, \ptr, .Lxchal_ofs_+56 - addi \ptr, \ptr, 64 - ae_l64.i aed17, \ptr, .Lxchal_ofs_+0 - ae_l64.i aed18, \ptr, .Lxchal_ofs_+8 - ae_l64.i aed19, \ptr, .Lxchal_ofs_+16 - ae_l64.i aed20, \ptr, .Lxchal_ofs_+24 - ae_l64.i aed21, \ptr, .Lxchal_ofs_+32 - ae_l64.i aed22, \ptr, .Lxchal_ofs_+40 - ae_l64.i aed23, \ptr, .Lxchal_ofs_+48 - ae_l64.i aed24, \ptr, .Lxchal_ofs_+56 - addi \ptr, \ptr, 64 - ae_l64.i aed25, \ptr, .Lxchal_ofs_+0 - ae_l64.i aed26, \ptr, .Lxchal_ofs_+8 - ae_l64.i aed27, \ptr, .Lxchal_ofs_+16 - ae_l64.i aed28, \ptr, .Lxchal_ofs_+24 - ae_l64.i aed29, \ptr, .Lxchal_ofs_+32 - ae_l64.i aed30, \ptr, .Lxchal_ofs_+40 - ae_l64.i aed31, \ptr, .Lxchal_ofs_+48 - addi \ptr, \ptr, 56 - l8ui \at1, \ptr, .Lxchal_ofs_+0 - ae_movea aep0, \at1 - l8ui \at1, \ptr, .Lxchal_ofs_+1 - ae_movea aep1, \at1 - l8ui \at1, \ptr, .Lxchal_ofs_+2 - ae_movea aep2, \at1 - l8ui \at1, \ptr, .Lxchal_ofs_+3 - ae_movea aep3, \at1 - addi \ptr, \ptr, 8 - ae_lalign128.i u0, \ptr, .Lxchal_ofs_+0 - ae_lalign128.i u1, \ptr, .Lxchal_ofs_+16 - ae_lalign128.i u2, \ptr, .Lxchal_ofs_+32 - ae_lalign128.i u3, \ptr, .Lxchal_ofs_+48 - .set .Lxchal_pofs_, .Lxchal_pofs_ + 320 - .set .Lxchal_ofs_, .Lxchal_ofs_ + 64 - .elseif ((XTHAL_SAS_TIE | XTHAL_SAS_NOCC | XTHAL_SAS_CALR) & ~(\alloc)) == 0 - xchal_sa_align \ptr, 0, 0, 16, 16 - .set .Lxchal_ofs_, .Lxchal_ofs_ + 384 - .endif - .endm // xchal_cp1_load - -#define XCHAL_CP1_NUM_ATMPS 1 -#define XCHAL_SA_NUM_ATMPS 1 - - /* Empty macros for unconfigured coprocessors: */ - .macro xchal_cp0_store p a b c d continue=0 ofs=-1 select=-1 ; .endm - .macro xchal_cp0_load p a b c d continue=0 ofs=-1 select=-1 ; .endm - .macro xchal_cp2_store p a b c d continue=0 ofs=-1 select=-1 ; .endm - .macro xchal_cp2_load p a b c d continue=0 ofs=-1 select=-1 ; .endm - .macro xchal_cp3_store p a b c d continue=0 ofs=-1 select=-1 ; .endm - .macro xchal_cp3_load p a b c d continue=0 ofs=-1 select=-1 ; .endm - .macro xchal_cp4_store p a b c d continue=0 ofs=-1 select=-1 ; .endm - .macro xchal_cp4_load p a b c d continue=0 ofs=-1 select=-1 ; .endm - .macro xchal_cp5_store p a b c d continue=0 ofs=-1 select=-1 ; .endm - .macro xchal_cp5_load p a b c d continue=0 ofs=-1 select=-1 ; .endm - .macro xchal_cp6_store p a b c d continue=0 ofs=-1 select=-1 ; .endm - .macro xchal_cp6_load p a b c d continue=0 ofs=-1 select=-1 ; .endm - .macro xchal_cp7_store p a b c d continue=0 ofs=-1 select=-1 ; .endm - .macro xchal_cp7_load p a b c d continue=0 ofs=-1 select=-1 ; .endm - -#endif /*_XTENSA_CORE_TIE_ASM_H*/ - diff --git a/src/platform/amd/acp_7_x/include/arch/xtensa/config/tie.h b/src/platform/amd/acp_7_x/include/arch/xtensa/config/tie.h deleted file mode 100644 index 3faa4ccfa7a1..000000000000 --- a/src/platform/amd/acp_7_x/include/arch/xtensa/config/tie.h +++ /dev/null @@ -1,211 +0,0 @@ -/* - * tie.h -- compile-time HAL definitions dependent on CORE & TIE configuration - * - * NOTE: This header file is not meant to be included directly. - */ - -/* This header file describes this specific Xtensa processor's TIE extensions - that extend basic Xtensa core functionality. It is customized to this - Xtensa processor configuration. - - Customer ID=20511; Build=0xb6494; Copyright (c) 1999-2024 Cadence Design Systems Inc. - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - -#ifndef XTENSA_CORE_TIE_H -#define XTENSA_CORE_TIE_H - -/* parasoft-begin-suppress ALL "This file not MISRA checked." */ - -#define XCHAL_CP_NUM 1 /* number of coprocessors */ -#define XCHAL_CP_MAX 2 /* max CP ID + 1 (0 if none) */ -#define XCHAL_CP_MASK 0x02 /* bitmask of all CPs by ID */ -#define XCHAL_CP_PORT_MASK 0x00 /* bitmask of only port CPs */ - -/* Basic parameters of each coprocessor: */ -#define XCHAL_CP1_NAME "AudioEngineLX" -#define XCHAL_CP1_IDENT AudioEngineLX -#define XCHAL_CP1_SA_SIZE 384 /* size of state save area */ -#define XCHAL_CP1_SA_ALIGN 16 /* min alignment of save area */ -#define XCHAL_CP_ID_AUDIOENGINELX 1 /* coprocessor ID (0..7) */ - -/* Filler info for unassigned coprocessors, to simplify arrays etc: */ -#define XCHAL_CP0_SA_SIZE 0 -#define XCHAL_CP0_SA_ALIGN 1 -#define XCHAL_CP2_SA_SIZE 0 -#define XCHAL_CP2_SA_ALIGN 1 -#define XCHAL_CP3_SA_SIZE 0 -#define XCHAL_CP3_SA_ALIGN 1 -#define XCHAL_CP4_SA_SIZE 0 -#define XCHAL_CP4_SA_ALIGN 1 -#define XCHAL_CP5_SA_SIZE 0 -#define XCHAL_CP5_SA_ALIGN 1 -#define XCHAL_CP6_SA_SIZE 0 -#define XCHAL_CP6_SA_ALIGN 1 -#define XCHAL_CP7_SA_SIZE 0 -#define XCHAL_CP7_SA_ALIGN 1 - -/* Save area for non-coprocessor optional and custom (TIE) state: */ -#define XCHAL_NCP_SA_SIZE 12 -#define XCHAL_NCP_SA_ALIGN 4 - -/* Total save area for optional and custom state (NCP + CPn): */ -#define XCHAL_TOTAL_SA_SIZE 400 /* with 16-byte align padding */ -#define XCHAL_TOTAL_SA_ALIGN 16 /* actual minimum alignment */ - -/* - * Detailed contents of save areas. - * NOTE: caller must define the XCHAL_SA_REG macro (not defined here) - * before expanding the XCHAL_xxx_SA_LIST() macros. - * - * XCHAL_SA_REG(s,ccused,abikind,kind,opt,name,galign,align,asize, - * dbnum,base,regnum,bitsz,gapsz,reset,x...) - * - * s = passed from XCHAL_*_LIST(s), eg. to select how to expand - * ccused = set if used by compiler without special options or code - * abikind = 0 (caller-saved), 1 (callee-saved), or 2 (thread-global) - * kind = 0 (special reg), 1 (TIE user reg), or 2 (TIE regfile reg) - * opt = 0 (custom TIE extension or coprocessor), or 1 (optional reg) - * name = lowercase reg name (no quotes) - * galign = group byte alignment (power of 2) (galign >= align) - * align = register byte alignment (power of 2) - * asize = allocated size in bytes (asize*8 == bitsz + gapsz + padsz) - * (not including any pad bytes required to galign this or next reg) - * dbnum = unique target number f/debug (see ) - * base = reg shortname w/o index (or sr=special, ur=TIE user reg) - * regnum = reg index in regfile, or special/TIE-user reg number - * bitsz = number of significant bits (regfile width, or ur/sr mask bits) - * gapsz = intervening bits, if bitsz bits not stored contiguously - * (padsz = pad bits at end [TIE regfile] or at msbits [ur,sr] of asize) - * reset = register reset value (or 0 if undefined at reset) - * x = reserved for future use (0 until then) - * - * To filter out certain registers, e.g. to expand only the non-global - * registers used by the compiler, you can do something like this: - * - * #define XCHAL_SA_REG(s,ccused,p...) SELCC##ccused(p) - * #define SELCC0(p...) - * #define SELCC1(abikind,p...) SELAK##abikind(p) - * #define SELAK0(p...) REG(p) - * #define SELAK1(p...) REG(p) - * #define SELAK2(p...) - * #define REG(kind,tie,name,galn,aln,asz,csz,dbnum,base,rnum,bsz,rst,x...) \ - * ...what you want to expand... - */ - -#define XCHAL_NCP_SA_NUM 3 -#define XCHAL_NCP_SA_LIST(s) \ - XCHAL_SA_REG(s,1,2,1,1, threadptr, 4, 4, 4,0x03E7, ur,231, 32,0,0,0) \ - XCHAL_SA_REG(s,0,0,0,1, br, 4, 4, 4,0x0204, sr,4 , 16,0,0,0) \ - XCHAL_SA_REG(s,0,0,1,0, xnne_rerr, 4, 4, 4,0x0300, ur,0 , 1,0,0,0) - -#define XCHAL_CP0_SA_NUM 0 -#define XCHAL_CP0_SA_LIST(s) /* empty */ - -#define XCHAL_CP1_SA_NUM 52 -#define XCHAL_CP1_SA_LIST(s) \ - XCHAL_SA_REG(s,0,0,1,0, ae_zbiasv8c,16, 8, 8,0x1029, ur,-1 , 16,0,0,0) \ - XCHAL_SA_REG(s,0,0,1,0, fcr_fsr, 8, 8, 8,0x102A, ur,-1 , 7,0,0,0) \ - XCHAL_SA_REG(s,0,0,1,0, ae_ovf_sar, 4, 4, 4,0x03F0, ur,240, 15,0,0,0) \ - XCHAL_SA_REG(s,0,0,1,0, ae_bithead, 4, 4, 4,0x03F1, ur,241, 32,0,0,0) \ - XCHAL_SA_REG(s,0,0,1,0,ae_ts_fts_bu_bp, 4, 4, 4,0x03F2, ur,242, 16,0,0,0) \ - XCHAL_SA_REG(s,0,0,1,0, ae_cw_sd_no, 4, 4, 4,0x03F3, ur,243, 29,0,0,0) \ - XCHAL_SA_REG(s,0,0,1,0, ae_cbegin0, 4, 4, 4,0x03F6, ur,246, 32,0,0,0) \ - XCHAL_SA_REG(s,0,0,1,0, ae_cend0, 4, 4, 4,0x03F7, ur,247, 32,0,0,0) \ - XCHAL_SA_REG(s,0,0,1,0, ae_cbegin1, 4, 4, 4,0x03F8, ur,248, 32,0,0,0) \ - XCHAL_SA_REG(s,0,0,1,0, ae_cend1, 4, 4, 4,0x03F9, ur,249, 32,0,0,0) \ - XCHAL_SA_REG(s,0,0,1,0, ae_cbegin2, 4, 4, 4,0x03FA, ur,250, 32,0,0,0) \ - XCHAL_SA_REG(s,0,0,1,0, ae_cend2, 4, 4, 4,0x03FB, ur,251, 32,0,0,0) \ - XCHAL_SA_REG(s,0,0,2,0, aed0, 8, 8, 8,0x1000, aed,0 , 64,0,0,0) \ - XCHAL_SA_REG(s,0,0,2,0, aed1, 8, 8, 8,0x1001, aed,1 , 64,0,0,0) \ - XCHAL_SA_REG(s,0,0,2,0, aed2, 8, 8, 8,0x1002, aed,2 , 64,0,0,0) \ - XCHAL_SA_REG(s,0,0,2,0, aed3, 8, 8, 8,0x1003, aed,3 , 64,0,0,0) \ - XCHAL_SA_REG(s,0,0,2,0, aed4, 8, 8, 8,0x1004, aed,4 , 64,0,0,0) \ - XCHAL_SA_REG(s,0,0,2,0, aed5, 8, 8, 8,0x1005, aed,5 , 64,0,0,0) \ - XCHAL_SA_REG(s,0,0,2,0, aed6, 8, 8, 8,0x1006, aed,6 , 64,0,0,0) \ - XCHAL_SA_REG(s,0,0,2,0, aed7, 8, 8, 8,0x1007, aed,7 , 64,0,0,0) \ - XCHAL_SA_REG(s,0,0,2,0, aed8, 8, 8, 8,0x1008, aed,8 , 64,0,0,0) \ - XCHAL_SA_REG(s,0,0,2,0, aed9, 8, 8, 8,0x1009, aed,9 , 64,0,0,0) \ - XCHAL_SA_REG(s,0,0,2,0, aed10, 8, 8, 8,0x100A, aed,10 , 64,0,0,0) \ - XCHAL_SA_REG(s,0,0,2,0, aed11, 8, 8, 8,0x100B, aed,11 , 64,0,0,0) \ - XCHAL_SA_REG(s,0,0,2,0, aed12, 8, 8, 8,0x100C, aed,12 , 64,0,0,0) \ - XCHAL_SA_REG(s,0,0,2,0, aed13, 8, 8, 8,0x100D, aed,13 , 64,0,0,0) \ - XCHAL_SA_REG(s,0,0,2,0, aed14, 8, 8, 8,0x100E, aed,14 , 64,0,0,0) \ - XCHAL_SA_REG(s,0,0,2,0, aed15, 8, 8, 8,0x100F, aed,15 , 64,0,0,0) \ - XCHAL_SA_REG(s,0,0,2,0, aed16, 8, 8, 8,0x1010, aed,16 , 64,0,0,0) \ - XCHAL_SA_REG(s,0,0,2,0, aed17, 8, 8, 8,0x1011, aed,17 , 64,0,0,0) \ - XCHAL_SA_REG(s,0,0,2,0, aed18, 8, 8, 8,0x1012, aed,18 , 64,0,0,0) \ - XCHAL_SA_REG(s,0,0,2,0, aed19, 8, 8, 8,0x1013, aed,19 , 64,0,0,0) \ - XCHAL_SA_REG(s,0,0,2,0, aed20, 8, 8, 8,0x1014, aed,20 , 64,0,0,0) \ - XCHAL_SA_REG(s,0,0,2,0, aed21, 8, 8, 8,0x1015, aed,21 , 64,0,0,0) \ - XCHAL_SA_REG(s,0,0,2,0, aed22, 8, 8, 8,0x1016, aed,22 , 64,0,0,0) \ - XCHAL_SA_REG(s,0,0,2,0, aed23, 8, 8, 8,0x1017, aed,23 , 64,0,0,0) \ - XCHAL_SA_REG(s,0,0,2,0, aed24, 8, 8, 8,0x1018, aed,24 , 64,0,0,0) \ - XCHAL_SA_REG(s,0,0,2,0, aed25, 8, 8, 8,0x1019, aed,25 , 64,0,0,0) \ - XCHAL_SA_REG(s,0,0,2,0, aed26, 8, 8, 8,0x101A, aed,26 , 64,0,0,0) \ - XCHAL_SA_REG(s,0,0,2,0, aed27, 8, 8, 8,0x101B, aed,27 , 64,0,0,0) \ - XCHAL_SA_REG(s,0,0,2,0, aed28, 8, 8, 8,0x101C, aed,28 , 64,0,0,0) \ - XCHAL_SA_REG(s,0,0,2,0, aed29, 8, 8, 8,0x101D, aed,29 , 64,0,0,0) \ - XCHAL_SA_REG(s,0,0,2,0, aed30, 8, 8, 8,0x101E, aed,30 , 64,0,0,0) \ - XCHAL_SA_REG(s,0,0,2,0, aed31, 8, 8, 8,0x101F, aed,31 , 64,0,0,0) \ - XCHAL_SA_REG(s,0,0,2,0, aep0, 1, 1, 1,0x1024, aep,0 , 8,0,0,0) \ - XCHAL_SA_REG(s,0,0,2,0, aep1, 1, 1, 1,0x1025, aep,1 , 8,0,0,0) \ - XCHAL_SA_REG(s,0,0,2,0, aep2, 1, 1, 1,0x1026, aep,2 , 8,0,0,0) \ - XCHAL_SA_REG(s,0,0,2,0, aep3, 1, 1, 1,0x1027, aep,3 , 8,0,0,0) \ - XCHAL_SA_REG(s,0,0,2,0, u0,16,16,16,0x1020, u,0 ,128,0,0,0) \ - XCHAL_SA_REG(s,0,0,2,0, u1,16,16,16,0x1021, u,1 ,128,0,0,0) \ - XCHAL_SA_REG(s,0,0,2,0, u2,16,16,16,0x1022, u,2 ,128,0,0,0) \ - XCHAL_SA_REG(s,0,0,2,0, u3,16,16,16,0x1023, u,3 ,128,0,0,0) - -#define XCHAL_CP2_SA_NUM 0 -#define XCHAL_CP2_SA_LIST(s) /* empty */ - -#define XCHAL_CP3_SA_NUM 0 -#define XCHAL_CP3_SA_LIST(s) /* empty */ - -#define XCHAL_CP4_SA_NUM 0 -#define XCHAL_CP4_SA_LIST(s) /* empty */ - -#define XCHAL_CP5_SA_NUM 0 -#define XCHAL_CP5_SA_LIST(s) /* empty */ - -#define XCHAL_CP6_SA_NUM 0 -#define XCHAL_CP6_SA_LIST(s) /* empty */ - -#define XCHAL_CP7_SA_NUM 0 -#define XCHAL_CP7_SA_LIST(s) /* empty */ - -/* Byte length of instruction from its first nibble (op0 field), per FLIX. */ -/* (not available, must use XCHAL_BYTE0_FORMAT_LENGTHS for this processor) */ -/* Byte length of instruction from its first byte, per FLIX. */ -#define XCHAL_BYTE0_FORMAT_LENGTHS \ - 3,3,3,3,3,3,3,3,2,2,2,2,2,2,6,16, 3,3,3,3,3,3,3,3,2,2,2,2,2,2,6,3,\ - 3,3,3,3,3,3,3,3,2,2,2,2,2,2,6,16, 3,3,3,3,3,3,3,3,2,2,2,2,2,2,6,8,\ - 3,3,3,3,3,3,3,3,2,2,2,2,2,2,6,16, 3,3,3,3,3,3,3,3,2,2,2,2,2,2,6,3,\ - 3,3,3,3,3,3,3,3,2,2,2,2,2,2,6,16, 3,3,3,3,3,3,3,3,2,2,2,2,2,2,6,8,\ - 3,3,3,3,3,3,3,3,2,2,2,2,2,2,6,16, 3,3,3,3,3,3,3,3,2,2,2,2,2,2,6,3,\ - 3,3,3,3,3,3,3,3,2,2,2,2,2,2,6,16, 3,3,3,3,3,3,3,3,2,2,2,2,2,2,6,8,\ - 3,3,3,3,3,3,3,3,2,2,2,2,2,2,6,16, 3,3,3,3,3,3,3,3,2,2,2,2,2,2,6,3,\ - 3,3,3,3,3,3,3,3,2,2,2,2,2,2,6,16, 3,3,3,3,3,3,3,3,2,2,2,2,2,2,6,8 - -/* parasoft-end-suppress ALL "This file not MISRA checked." */ - -#endif /* XTENSA_CORE_TIE_H */ - diff --git a/src/platform/amd/acp_7_x/include/platform/lib/memory.h b/src/platform/amd/acp_7_x/include/platform/lib/memory.h index dd407a18e086..df1007e50674 100644 --- a/src/platform/amd/acp_7_x/include/platform/lib/memory.h +++ b/src/platform/amd/acp_7_x/include/platform/lib/memory.h @@ -231,11 +231,6 @@ struct sof; #define SHARED_DATA void platform_init_memmap(struct sof *sof); -static inline void *platform_shared_get(void *ptr, int bytes) -{ - return ptr; -} - static inline void *platform_rfree_prepare(void *ptr) { return ptr; diff --git a/src/platform/amd/rembrandt/include/platform/lib/memory.h b/src/platform/amd/rembrandt/include/platform/lib/memory.h index 92ac53b4c2ed..bd618b6d9bdb 100644 --- a/src/platform/amd/rembrandt/include/platform/lib/memory.h +++ b/src/platform/amd/rembrandt/include/platform/lib/memory.h @@ -156,11 +156,6 @@ struct sof; #define SHARED_DATA void platform_init_memmap(struct sof *sof); -static inline void *platform_shared_get(void *ptr, int bytes) -{ - return ptr; -} - static inline void *platform_rfree_prepare(void *ptr) { return ptr; diff --git a/src/platform/amd/renoir/include/platform/lib/memory.h b/src/platform/amd/renoir/include/platform/lib/memory.h index 4caeb0e30d06..0d1df900e6fd 100644 --- a/src/platform/amd/renoir/include/platform/lib/memory.h +++ b/src/platform/amd/renoir/include/platform/lib/memory.h @@ -151,11 +151,6 @@ struct sof; #define SHARED_DATA void platform_init_memmap(struct sof *sof); -static inline void *platform_shared_get(void *ptr, int bytes) -{ - return ptr; -} - static inline void *platform_rfree_prepare(void *ptr) { return ptr; diff --git a/src/platform/amd/renoir/lib/clk.c b/src/platform/amd/renoir/lib/clk.c index dddc974f631a..4880daafac71 100644 --- a/src/platform/amd/renoir/lib/clk.c +++ b/src/platform/amd/renoir/lib/clk.c @@ -122,7 +122,7 @@ void platform_clock_init(struct sof *sof) { int i; - sof->clocks = platform_shared_get(platform_clocks_info, sizeof(platform_clocks_info)); + sof->clocks = platform_clocks_info; for (i = 0; i < PLATFORM_CORE_COUNT; i++) { sof->clocks[i] = (struct clock_info) { diff --git a/src/platform/amd/renoir/lib/memory.c b/src/platform/amd/renoir/lib/memory.c index 9f16b1977f05..a5b33cbb24d6 100644 --- a/src/platform/amd/renoir/lib/memory.c +++ b/src/platform/amd/renoir/lib/memory.c @@ -91,5 +91,5 @@ static SHARED_DATA struct mm memmap = { void platform_init_memmap(struct sof *sof) { /* memmap has been initialized statically as a part of .data */ - sof->memory_map = platform_shared_get(&memmap, sizeof(memmap)); + sof->memory_map = &memmap; } diff --git a/src/platform/amd/renoir/platform.c b/src/platform/amd/renoir/platform.c index ad2fa16cf0a0..419f1251bd84 100644 --- a/src/platform/amd/renoir/platform.c +++ b/src/platform/amd/renoir/platform.c @@ -133,7 +133,7 @@ int platform_init(struct sof *sof) { int ret; - sof->platform_timer = platform_shared_get(&timer_shared, sizeof(timer_shared)); + sof->platform_timer = &timer_shared; sof->cpu_timers = sof->platform_timer; /* to view system memory */ diff --git a/src/platform/amd/vangogh/include/platform/lib/memory.h b/src/platform/amd/vangogh/include/platform/lib/memory.h index 6e0b768cc57a..49a12236833c 100644 --- a/src/platform/amd/vangogh/include/platform/lib/memory.h +++ b/src/platform/amd/vangogh/include/platform/lib/memory.h @@ -150,11 +150,6 @@ struct sof; #define SHARED_DATA void platform_init_memmap(struct sof *sof); -static inline void *platform_shared_get(void *ptr, int bytes) -{ - return ptr; -} - static inline void *platform_rfree_prepare(void *ptr) { return ptr; diff --git a/src/platform/imx8/include/platform/lib/memory.h b/src/platform/imx8/include/platform/lib/memory.h index 1c3d88fe4d4d..674d1af2611f 100644 --- a/src/platform/imx8/include/platform/lib/memory.h +++ b/src/platform/imx8/include/platform/lib/memory.h @@ -187,11 +187,6 @@ struct sof; void platform_init_memmap(struct sof *sof); -static inline void *platform_shared_get(void *ptr, int bytes) -{ - return ptr; -} - #define uncache_to_cache(address) address #define cache_to_uncache(address) address #define cache_to_uncache_init(address) address diff --git a/src/platform/imx8/lib/clk.c b/src/platform/imx8/lib/clk.c index 45edfe98461d..152cac1752d7 100644 --- a/src/platform/imx8/lib/clk.c +++ b/src/platform/imx8/lib/clk.c @@ -30,7 +30,7 @@ void platform_clock_init(struct sof *sof) { int i; - sof->clocks = platform_shared_get(platform_clocks_info, sizeof(platform_clocks_info)); + sof->clocks = platform_clocks_info; for (i = 0; i < CONFIG_CORE_COUNT; i++) { sof->clocks[i] = (struct clock_info) { diff --git a/src/platform/imx8/lib/memory.c b/src/platform/imx8/lib/memory.c index 78cb05c86cbb..d4527d756ac1 100644 --- a/src/platform/imx8/lib/memory.c +++ b/src/platform/imx8/lib/memory.c @@ -95,5 +95,5 @@ static SHARED_DATA struct mm memmap = { void platform_init_memmap(struct sof *sof) { /* memmap has been initialized statically as a part of .data */ - sof->memory_map = platform_shared_get(&memmap, sizeof(memmap)); + sof->memory_map = &memmap; } diff --git a/src/platform/imx8m/include/platform/lib/memory.h b/src/platform/imx8m/include/platform/lib/memory.h index 6b2c00f89bad..490e65a0052d 100644 --- a/src/platform/imx8m/include/platform/lib/memory.h +++ b/src/platform/imx8m/include/platform/lib/memory.h @@ -211,11 +211,6 @@ void platform_init_memmap(struct sof *sof); #define cache_to_uncache_init(address) address #define is_uncached(address) 0 -static inline void *platform_shared_get(void *ptr, int bytes) -{ - return ptr; -} - /** * \brief Function for keeping shared data synchronized. * It's used after usage of data shared by different cores. diff --git a/src/platform/imx8m/lib/clk.c b/src/platform/imx8m/lib/clk.c index 37b115e507a2..78aacfd05e28 100644 --- a/src/platform/imx8m/lib/clk.c +++ b/src/platform/imx8m/lib/clk.c @@ -25,7 +25,7 @@ void platform_clock_init(struct sof *sof) { int i; - sof->clocks = platform_shared_get(platform_clocks_info, sizeof(platform_clocks_info)); + sof->clocks = platform_clocks_info; for (i = 0; i < CONFIG_CORE_COUNT; i++) { sof->clocks[i] = (struct clock_info) { diff --git a/src/platform/imx8m/lib/memory.c b/src/platform/imx8m/lib/memory.c index 85596ca2b525..9f26cd3ebe80 100644 --- a/src/platform/imx8m/lib/memory.c +++ b/src/platform/imx8m/lib/memory.c @@ -97,5 +97,5 @@ static SHARED_DATA struct mm memmap = { void platform_init_memmap(struct sof *sof) { /* memmap has been initialized statically as a part of .data */ - sof->memory_map = platform_shared_get(&memmap, sizeof(memmap)); + sof->memory_map = &memmap; } diff --git a/src/platform/imx8m_cm7/include/platform/lib/memory.h b/src/platform/imx8m_cm7/include/platform/lib/memory.h index 413e77f142e1..af0b0b823e24 100644 --- a/src/platform/imx8m_cm7/include/platform/lib/memory.h +++ b/src/platform/imx8m_cm7/include/platform/lib/memory.h @@ -28,11 +28,6 @@ /* WAKEUP domain MU1 side B */ #define MU_BASE 0x30AB0000UL -static inline void *platform_shared_get(void *ptr, int bytes) -{ - return ptr; -} - #endif /* __PLATFORM_LIB_MEMORY_H__ */ #else diff --git a/src/platform/imx8ulp/include/platform/lib/memory.h b/src/platform/imx8ulp/include/platform/lib/memory.h index 00aa9b8b2a41..ee74e6989e86 100644 --- a/src/platform/imx8ulp/include/platform/lib/memory.h +++ b/src/platform/imx8ulp/include/platform/lib/memory.h @@ -195,11 +195,6 @@ void platform_init_memmap(struct sof *sof); #define cache_to_uncache_init(address) address #define is_uncached(address) 0 -static inline void *platform_shared_get(void *ptr, int bytes) -{ - return ptr; -} - /** * \brief Function for keeping shared data synchronized. * It's used after usage of data shared by different cores. diff --git a/src/platform/imx8ulp/lib/clk.c b/src/platform/imx8ulp/lib/clk.c index 21d1f4166188..93a0a83ed54d 100644 --- a/src/platform/imx8ulp/lib/clk.c +++ b/src/platform/imx8ulp/lib/clk.c @@ -25,7 +25,7 @@ void platform_clock_init(struct sof *sof) { int i; - sof->clocks = platform_shared_get(platform_clocks_info, sizeof(platform_clocks_info)); + sof->clocks = platform_clocks_info; for (i = 0; i < CONFIG_CORE_COUNT; i++) { sof->clocks[i] = (struct clock_info) { diff --git a/src/platform/imx8ulp/lib/memory.c b/src/platform/imx8ulp/lib/memory.c index ac80bf55aae6..cafd4b5d3d0c 100644 --- a/src/platform/imx8ulp/lib/memory.c +++ b/src/platform/imx8ulp/lib/memory.c @@ -96,5 +96,5 @@ static SHARED_DATA struct mm memmap = { void platform_init_memmap(struct sof *sof) { /* memmap has been initialized statically as a part of .data */ - sof->memory_map = platform_shared_get(&memmap, sizeof(memmap)); + sof->memory_map = &memmap; } diff --git a/src/platform/imx93_a55/include/platform/lib/memory.h b/src/platform/imx93_a55/include/platform/lib/memory.h index 77ca3ec6103b..b9c84ff42a7f 100644 --- a/src/platform/imx93_a55/include/platform/lib/memory.h +++ b/src/platform/imx93_a55/include/platform/lib/memory.h @@ -58,11 +58,6 @@ /* WM8962 is connected to SAI3 */ #define SAI3_BASE 0x42660000 -static inline void *platform_shared_get(void *ptr, int bytes) -{ - return ptr; -} - #endif /* __PLATFORM_LIB_MEMORY_H__ */ #else diff --git a/src/platform/imx95/include/platform/lib/memory.h b/src/platform/imx95/include/platform/lib/memory.h index bf3ff05a5c69..552c20e32530 100644 --- a/src/platform/imx95/include/platform/lib/memory.h +++ b/src/platform/imx95/include/platform/lib/memory.h @@ -28,11 +28,6 @@ /* WAKEUP domain MU7 side B */ #define MU_BASE 0x42440000UL -static inline void *platform_shared_get(void *ptr, int bytes) -{ - return ptr; -} - #endif /* __PLATFORM_LIB_MEMORY_H__ */ #else diff --git a/src/platform/intel/ace/include/ace/lib/memory.h b/src/platform/intel/ace/include/ace/lib/memory.h index 357397b2affa..f5740a7d659d 100644 --- a/src/platform/intel/ace/include/ace/lib/memory.h +++ b/src/platform/intel/ace/include/ace/lib/memory.h @@ -33,21 +33,6 @@ #define cache_to_uncache(address) sys_cache_uncached_ptr_get(address) #define is_uncached(address) (!sys_cache_is_ptr_cached(address)) -/** - * \brief Returns pointer to the memory shared by multiple cores. - * \param[in,out] ptr Initial pointer to the allocated memory. - * \param[in] bytes Size of the allocated memory - * \return Appropriate pointer to the shared memory. - * - * This function is called only once right after allocation of shared memory. - * Platforms with uncached memory region should return aliased address. - * On platforms without such region simple invalidate is enough. - */ -static inline void *platform_shared_get(void *ptr, int bytes) -{ - return ptr; -} - #endif /* __ACE_LIB_MEMORY_H__ */ #else diff --git a/src/platform/intel/ace/lib/watchdog.c b/src/platform/intel/ace/lib/watchdog.c index dcae80da801d..a9efb2ae0b7e 100644 --- a/src/platform/intel/ace/lib/watchdog.c +++ b/src/platform/intel/ace/lib/watchdog.c @@ -5,6 +5,7 @@ * Author: Adrian Warecki */ +#include #include #include #include @@ -72,7 +73,7 @@ static void watchdog_timeout(const struct device *dev, int core) watchdog_secondary_core_action_on_timeout(); } -void watchdog_init(void) +__cold void watchdog_init(void) { int i, ret; const struct wdt_timeout_cfg watchdog_config = { @@ -80,6 +81,8 @@ void watchdog_init(void) .callback = watchdog_timeout, }; + assert_can_be_cold(); + secondary_timeout_ipc.tx_data = NULL; secondary_timeout_ipc.tx_size = 0; list_init(&secondary_timeout_ipc.list); diff --git a/src/platform/intel/ace/platform.c b/src/platform/intel/ace/platform.c index 1fde2713049d..f19003117e08 100644 --- a/src/platform/intel/ace/platform.c +++ b/src/platform/intel/ace/platform.c @@ -65,6 +65,8 @@ __cold int platform_boot_complete(uint32_t boot_message) { struct ipc_cmd_hdr header; + assert_can_be_cold(); + /* get any IPC specific boot message and optional data */ ipc_boot_complete_msg(&header, 0); header.pri |= boot_message; @@ -92,6 +94,8 @@ __cold int platform_init(struct sof *sof) { int ret; + assert_can_be_cold(); + trace_point(TRACE_BOOT_PLATFORM_CLOCK); platform_clock_init(sof); diff --git a/src/platform/intel/cavs/include/cavs/lib/memory.h b/src/platform/intel/cavs/include/cavs/lib/memory.h index b53c76d64b6d..0473131da6c8 100644 --- a/src/platform/intel/cavs/include/cavs/lib/memory.h +++ b/src/platform/intel/cavs/include/cavs/lib/memory.h @@ -75,26 +75,6 @@ static inline void *cache_to_uncache(void __sparse_cache *address) #define cache_to_uncache_init(address) address #endif -/** - * \brief Returns pointer to the memory shared by multiple cores. - * \param[in,out] ptr Initial pointer to the allocated memory. - * \param[in] bytes Size of the allocated memory - * \return Appropriate pointer to the shared memory. - * - * This function is called only once right after allocation of shared memory. - * Platforms with uncached memory region should return aliased address. - * On platforms without such region simple invalidate is enough. - */ -static inline void *platform_shared_get(void *ptr, int bytes) -{ -#if CONFIG_CORE_COUNT > 1 && !defined __ZEPHYR__ - dcache_invalidate_region((__sparse_force void __sparse_cache *)ptr, bytes); - return cache_to_uncache(ptr); -#else - return ptr; -#endif -} - /** * \brief Transforms pointer if necessary before freeing the memory. * \param[in,out] ptr Pointer to the allocated memory. diff --git a/src/platform/library/include/platform/lib/memory.h b/src/platform/library/include/platform/lib/memory.h index fafb3df303f7..11f0114c1dde 100644 --- a/src/platform/library/include/platform/lib/memory.h +++ b/src/platform/library/include/platform/lib/memory.h @@ -37,11 +37,6 @@ uint8_t *get_library_mailbox(void); #define SHARED_DATA -static inline void *platform_shared_get(void *ptr, int bytes) -{ - return ptr; -} - void platform_init_memmap(struct sof *sof); static inline void *platform_rfree_prepare(void *ptr) diff --git a/src/platform/library/schedule/edf_schedule.c b/src/platform/library/schedule/edf_schedule.c index 0c127c934e21..67c3a669c2f8 100644 --- a/src/platform/library/schedule/edf_schedule.c +++ b/src/platform/library/schedule/edf_schedule.c @@ -49,10 +49,12 @@ static int schedule_edf_task(void *data, struct task *task, uint64_t start, return 0; } +#if CONFIG_SOF_BOOT_TEST_STANDALONE || CONFIG_LIBRARY static void edf_scheduler_free(void *data, uint32_t flags) { free(data); } +#endif static int schedule_edf_task_cancel(void *data, struct task *task) { @@ -83,7 +85,9 @@ static struct scheduler_ops schedule_edf_ops = { .reschedule_task = NULL, .schedule_task_cancel = schedule_edf_task_cancel, .schedule_task_free = schedule_edf_task_free, +#if CONFIG_SOF_BOOT_TEST_STANDALONE || CONFIG_LIBRARY .scheduler_free = edf_scheduler_free, +#endif }; int schedule_task_init_edf(struct task *task, const struct sof_uuid_entry *uid, diff --git a/src/platform/library/schedule/ll_schedule.c b/src/platform/library/schedule/ll_schedule.c index 4cfd0a9d196c..1bfc378ffad2 100644 --- a/src/platform/library/schedule/ll_schedule.c +++ b/src/platform/library/schedule/ll_schedule.c @@ -66,10 +66,12 @@ static int schedule_ll_task(void *data, struct task *task, uint64_t start, return 0; } +#if CONFIG_SOF_BOOT_TEST_STANDALONE || CONFIG_LIBRARY static void ll_scheduler_free(void *data, uint32_t flags) { free(data); } +#endif /* TODO: scheduler free and cancel APIs can merge as part of Zephyr */ static int schedule_ll_task_cancel(void *data, struct task *task) @@ -96,7 +98,9 @@ static struct scheduler_ops schedule_ll_ops = { .reschedule_task = NULL, .schedule_task_cancel = schedule_ll_task_cancel, .schedule_task_free = schedule_ll_task_free, +#if CONFIG_SOF_BOOT_TEST_STANDALONE || CONFIG_LIBRARY .scheduler_free = ll_scheduler_free, +#endif }; int schedule_task_init_ll(struct task *task, diff --git a/src/platform/library/schedule/schedule.c b/src/platform/library/schedule/schedule.c index b1dfcbb19445..f110711afe35 100644 --- a/src/platform/library/schedule/schedule.c +++ b/src/platform/library/schedule/schedule.c @@ -25,9 +25,29 @@ int schedule_task_init(struct task *task, uint16_t priority, enum task_state (*run)(void *data), void *data, uint16_t core, uint32_t flags) { + struct schedulers *schedulers = *arch_schedulers_get(); + struct schedule_data *sch = NULL; + struct list_item *slist; + if (type >= SOF_SCHEDULE_COUNT) return -EINVAL; + if (!schedulers) + return -ENODEV; + + task->sch = NULL; + list_for_item(slist, &schedulers->list) { + sch = container_of(slist, struct schedule_data, list); + if (type == sch->type) { + task->sch = sch; + break; + } + } + + if (!task->sch) { + return -ENODEV; + } + task->uid = uid; task->type = type; task->priority = priority; diff --git a/src/platform/mt8186/include/platform/lib/memory.h b/src/platform/mt8186/include/platform/lib/memory.h index 65148c9c348d..305e4c917846 100644 --- a/src/platform/mt8186/include/platform/lib/memory.h +++ b/src/platform/mt8186/include/platform/lib/memory.h @@ -192,11 +192,6 @@ struct sof; void platform_init_memmap(struct sof *sof); -static inline void *platform_shared_get(void *ptr, int bytes) -{ - return ptr; -} - #define uncache_to_cache(address) address #define cache_to_uncache(address) address #define cache_to_uncache_init(address) address diff --git a/src/platform/mt8186/lib/clk.c b/src/platform/mt8186/lib/clk.c index 033580a280bf..6a5b2910c506 100644 --- a/src/platform/mt8186/lib/clk.c +++ b/src/platform/mt8186/lib/clk.c @@ -135,7 +135,7 @@ void platform_clock_init(struct sof *sof) { int i; - sof->clocks = platform_shared_get(platform_clocks_info, sizeof(platform_clocks_info)); + sof->clocks = platform_clocks_info; for (i = 0; i < CONFIG_CORE_COUNT; i++) { sof->clocks[i] = (struct clock_info){ diff --git a/src/platform/mt8186/lib/memory.c b/src/platform/mt8186/lib/memory.c index 2a01e0333bab..1ffc41859713 100644 --- a/src/platform/mt8186/lib/memory.c +++ b/src/platform/mt8186/lib/memory.c @@ -98,5 +98,5 @@ static SHARED_DATA struct mm memmap = { void platform_init_memmap(struct sof *sof) { /* memmap has been initialized statically as a part of .data */ - sof->memory_map = platform_shared_get(&memmap, sizeof(memmap)); + sof->memory_map = &memmap; } diff --git a/src/platform/mt8186/platform.c b/src/platform/mt8186/platform.c index f171e6cb7d0b..e08766eba70d 100644 --- a/src/platform/mt8186/platform.c +++ b/src/platform/mt8186/platform.c @@ -163,7 +163,7 @@ int platform_init(struct sof *sof) { int ret; - sof->platform_timer = platform_shared_get(&timer_shared, sizeof(timer_shared)); + sof->platform_timer = &timer_shared; sof->cpu_timers = sof->platform_timer; platform_interrupt_init(); diff --git a/src/platform/mt8188/include/platform/lib/memory.h b/src/platform/mt8188/include/platform/lib/memory.h index 65148c9c348d..305e4c917846 100644 --- a/src/platform/mt8188/include/platform/lib/memory.h +++ b/src/platform/mt8188/include/platform/lib/memory.h @@ -192,11 +192,6 @@ struct sof; void platform_init_memmap(struct sof *sof); -static inline void *platform_shared_get(void *ptr, int bytes) -{ - return ptr; -} - #define uncache_to_cache(address) address #define cache_to_uncache(address) address #define cache_to_uncache_init(address) address diff --git a/src/platform/mt8188/lib/clk.c b/src/platform/mt8188/lib/clk.c index f39872fca845..bd97dad069a4 100644 --- a/src/platform/mt8188/lib/clk.c +++ b/src/platform/mt8188/lib/clk.c @@ -123,7 +123,7 @@ void platform_clock_init(struct sof *sof) { int i; - sof->clocks = platform_shared_get(platform_clocks_info, sizeof(platform_clocks_info)); + sof->clocks = platform_clocks_info; for (i = 0; i < CONFIG_CORE_COUNT; i++) { sof->clocks[i] = (struct clock_info){ diff --git a/src/platform/mt8188/lib/memory.c b/src/platform/mt8188/lib/memory.c index 73d3af0f4d43..5ef0828ffc6c 100644 --- a/src/platform/mt8188/lib/memory.c +++ b/src/platform/mt8188/lib/memory.c @@ -98,5 +98,5 @@ static SHARED_DATA struct mm memmap = { void platform_init_memmap(struct sof *sof) { /* memmap has been initialized statically as a part of .data */ - sof->memory_map = platform_shared_get(&memmap, sizeof(memmap)); + sof->memory_map = &memmap; } diff --git a/src/platform/mt8188/platform.c b/src/platform/mt8188/platform.c index fc2e83dbed25..d3433a3b46c8 100644 --- a/src/platform/mt8188/platform.c +++ b/src/platform/mt8188/platform.c @@ -161,7 +161,7 @@ int platform_init(struct sof *sof) { int ret; - sof->platform_timer = platform_shared_get(&timer_shared, sizeof(timer_shared)); + sof->platform_timer = &timer_shared; sof->cpu_timers = sof->platform_timer; platform_interrupt_init(); diff --git a/src/platform/mt8195/include/platform/lib/memory.h b/src/platform/mt8195/include/platform/lib/memory.h index 42ee7f7990ab..7a93d36d1b2a 100644 --- a/src/platform/mt8195/include/platform/lib/memory.h +++ b/src/platform/mt8195/include/platform/lib/memory.h @@ -183,11 +183,6 @@ struct sof; void platform_init_memmap(struct sof *sof); -static inline void *platform_shared_get(void *ptr, int bytes) -{ - return ptr; -} - #define uncache_to_cache(address) address #define cache_to_uncache(address) address #define cache_to_uncache_init(address) address diff --git a/src/platform/mt8195/lib/clk.c b/src/platform/mt8195/lib/clk.c index 1514b9d9307d..894396ab4855 100644 --- a/src/platform/mt8195/lib/clk.c +++ b/src/platform/mt8195/lib/clk.c @@ -177,7 +177,7 @@ void platform_clock_init(struct sof *sof) { int i; - sof->clocks = platform_shared_get(platform_clocks_info, sizeof(platform_clocks_info)); + sof->clocks = platform_clocks_info; for (i = 0; i < CONFIG_CORE_COUNT; i++) { sof->clocks[i] = (struct clock_info){ diff --git a/src/platform/mt8195/lib/memory.c b/src/platform/mt8195/lib/memory.c index b2d545ff6f1d..c452a0520cc7 100644 --- a/src/platform/mt8195/lib/memory.c +++ b/src/platform/mt8195/lib/memory.c @@ -97,5 +97,5 @@ static SHARED_DATA struct mm memmap = { void platform_init_memmap(struct sof *sof) { /* memmap has been initialized statically as a part of .data */ - sof->memory_map = platform_shared_get(&memmap, sizeof(memmap)); + sof->memory_map = &memmap; } diff --git a/src/platform/mt8195/platform.c b/src/platform/mt8195/platform.c index 7e713290437d..6a08580ad043 100644 --- a/src/platform/mt8195/platform.c +++ b/src/platform/mt8195/platform.c @@ -182,7 +182,7 @@ int platform_init(struct sof *sof) { int ret; - sof->platform_timer = platform_shared_get(&timer_shared, sizeof(timer_shared)); + sof->platform_timer = &timer_shared; sof->cpu_timers = sof->platform_timer; platform_interrupt_init(); diff --git a/src/platform/mt8196/include/platform/lib/memory.h b/src/platform/mt8196/include/platform/lib/memory.h index f6d8bd521208..b849fe09cb4f 100644 --- a/src/platform/mt8196/include/platform/lib/memory.h +++ b/src/platform/mt8196/include/platform/lib/memory.h @@ -191,11 +191,6 @@ struct sof; void platform_init_memmap(struct sof *sof); -static inline void *platform_shared_get(void *ptr, int bytes) -{ - return ptr; -} - #define uncache_to_cache(address) address #define cache_to_uncache(address) address #define cache_to_uncache_init(address) address diff --git a/src/platform/mt8196/lib/clk.c b/src/platform/mt8196/lib/clk.c index 69af725dec73..ebf109a0c9fc 100644 --- a/src/platform/mt8196/lib/clk.c +++ b/src/platform/mt8196/lib/clk.c @@ -37,7 +37,7 @@ void platform_clock_init(struct sof *sof) int i; tr_dbg(&clkdrv_tr, "clock init\n"); - sof->clocks = platform_shared_get(platform_clocks_info, sizeof(platform_clocks_info)); + sof->clocks = platform_clocks_info; /* When the system is in an active state, the DSP clock operates at 800MHz (0.75V). * In a low power scenario, the DSP enters WFI state, and the clock reduces to 26MHz. diff --git a/src/platform/mt8196/lib/memory.c b/src/platform/mt8196/lib/memory.c index dca28790d741..7445f1713830 100644 --- a/src/platform/mt8196/lib/memory.c +++ b/src/platform/mt8196/lib/memory.c @@ -97,5 +97,5 @@ static SHARED_DATA struct mm memmap = { void platform_init_memmap(struct sof *sof) { /* memmap has been initialized statically as a part of .data */ - sof->memory_map = platform_shared_get(&memmap, sizeof(memmap)); + sof->memory_map = &memmap; } diff --git a/src/platform/mt8196/platform.c b/src/platform/mt8196/platform.c index fc7cd9a7eb34..c093e7b003ef 100644 --- a/src/platform/mt8196/platform.c +++ b/src/platform/mt8196/platform.c @@ -162,7 +162,7 @@ int platform_init(struct sof *sof) { int ret; - sof->platform_timer = platform_shared_get(&timer_shared, sizeof(timer_shared)); + sof->platform_timer = &timer_shared; sof->cpu_timers = sof->platform_timer; platform_interrupt_init(); platform_clock_init(sof); diff --git a/src/platform/mt8365/include/platform/lib/memory.h b/src/platform/mt8365/include/platform/lib/memory.h index 3f0532d4d417..75965466ae95 100644 --- a/src/platform/mt8365/include/platform/lib/memory.h +++ b/src/platform/mt8365/include/platform/lib/memory.h @@ -206,11 +206,6 @@ struct sof; void platform_init_memmap(struct sof *sof); -static inline void *platform_shared_get(void *ptr, int bytes) -{ - return ptr; -} - #define uncache_to_cache(address) address #define cache_to_uncache(address) address #define cache_to_uncache_init(address) address diff --git a/src/platform/mt8365/lib/clk.c b/src/platform/mt8365/lib/clk.c index 8101d4fd0f2c..57bc821049fb 100644 --- a/src/platform/mt8365/lib/clk.c +++ b/src/platform/mt8365/lib/clk.c @@ -168,7 +168,7 @@ void platform_clock_init(struct sof *sof) { int i; - sof->clocks = platform_shared_get(platform_clocks_info, sizeof(platform_clocks_info)); + sof->clocks = platform_clocks_info; for (i = 0; i < CONFIG_CORE_COUNT; i++) { sof->clocks[i] = (struct clock_info){ diff --git a/src/platform/mt8365/lib/memory.c b/src/platform/mt8365/lib/memory.c index 13c7a9b2bdd4..7ed1417dc1f2 100644 --- a/src/platform/mt8365/lib/memory.c +++ b/src/platform/mt8365/lib/memory.c @@ -97,5 +97,5 @@ static SHARED_DATA struct mm memmap = { void platform_init_memmap(struct sof *sof) { /* memmap has been initialized statically as a part of .data */ - sof->memory_map = platform_shared_get(&memmap, sizeof(memmap)); + sof->memory_map = &memmap; } diff --git a/src/platform/mt8365/platform.c b/src/platform/mt8365/platform.c index d57edc2f8431..5ae6378c5eb5 100644 --- a/src/platform/mt8365/platform.c +++ b/src/platform/mt8365/platform.c @@ -164,7 +164,7 @@ int platform_init(struct sof *sof) mailbox_sw_reg_write(SRAM_REG_OP_CPU2DSP, 0); mailbox_sw_reg_write(SRAM_REG_OP_DSP2CPU, 0); - sof->platform_timer = platform_shared_get(&timer_shared, sizeof(timer_shared)); + sof->platform_timer = &timer_shared; sof->cpu_timers = sof->platform_timer; platform_interrupt_init(); diff --git a/src/platform/mtk/include/platform/lib/memory.h b/src/platform/mtk/include/platform/lib/memory.h index 26806ab394ba..1e3acbee6912 100644 --- a/src/platform/mtk/include/platform/lib/memory.h +++ b/src/platform/mtk/include/platform/lib/memory.h @@ -16,11 +16,6 @@ BUILD_ASSERT(PLATFORM_DCACHE_ALIGN == XCHAL_DCACHE_LINESIZE); #define uncache_to_cache(addr) (addr) #define cache_to_uncache(addr) (addr) -static inline void *platform_shared_get(void *ptr, int bytes) -{ - return ptr; -} - #define host_to_local(addr) (addr) #define PLATFORM_HEAP_SYSTEM 1 diff --git a/src/platform/posix/fuzz.c b/src/platform/posix/fuzz.c index e5c515c15a45..e60d8e549035 100644 --- a/src/platform/posix/fuzz.c +++ b/src/platform/posix/fuzz.c @@ -10,27 +10,48 @@ #include #include #include +#include const uint8_t *posix_fuzz_buf; size_t posix_fuzz_sz; +/* Number of simulator quanta the budget is split into for the + * drain-or-abort loop. More quanta = earlier exit on quick testcases. + */ +#define POSIX_FUZZ_DRAIN_QUANTA 8 + /** * Entry point for fuzzing. Works by placing the data * into two known symbols, triggering an app-visible interrupt, and - * then letting the simulator run for a fixed amount of time (intended to be - * "long enough" to handle the event and reach a quiescent state - * again) + * then letting the simulator run for up to a fixed amount of time + * split into small quanta, exiting as soon as the OS has drained the + * staged fuzz input. If the budget is exhausted before drain we drop + * pending state to keep testcases isolated. */ NATIVE_SIMULATOR_IF int LLVMFuzzerTestOneInput(const uint8_t *data, size_t sz) { static bool runner_initialized; + uint64_t total_us; + uint64_t quantum_us; + int i; if (!runner_initialized) { nsi_init(0, NULL); runner_initialized = true; } + total_us = k_ticks_to_us_ceil64(CONFIG_ZEPHYR_POSIX_FUZZ_TICKS); + quantum_us = (total_us + POSIX_FUZZ_DRAIN_QUANTA - 1) / POSIX_FUZZ_DRAIN_QUANTA; + if (quantum_us == 0) + quantum_us = 1; + + /* Fresh testcase: drop any leftovers from a previous case before + * staging the new buffer, so state from the prior input cannot + * influence this one. + */ + posix_fuzz_case_begin(); + /* Provide the fuzz data to the embedded OS as an interrupt, with * "DMA-like" data placed into posix_fuzz_buf/sz. */ @@ -38,9 +59,19 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t sz) posix_fuzz_sz = sz; hw_irq_ctrl_set_irq(CONFIG_ZEPHYR_POSIX_FUZZ_IRQ); - /* Give the OS time to process whatever happened in that - * interrupt and reach an idle state. + /* Bounded drain loop: run simulator in small quanta and exit + * as soon as both the raw input buffer and the staged IPC + * queue are empty. + */ + for (i = 0; i < POSIX_FUZZ_DRAIN_QUANTA; i++) { + nsi_exec_for(quantum_us); + if (!posix_fuzz_case_pending()) + return 0; + } + + /* Budget exhausted without full drain: hard reset so the next + * testcase starts clean. */ - nsi_exec_for(k_ticks_to_us_ceil64(CONFIG_ZEPHYR_POSIX_FUZZ_TICKS)); + posix_fuzz_case_abort(); return 0; } diff --git a/src/platform/posix/include/platform/fuzz.h b/src/platform/posix/include/platform/fuzz.h new file mode 100644 index 000000000000..91f28b3285d2 --- /dev/null +++ b/src/platform/posix/include/platform/fuzz.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* Copyright(c) 2026 Intel Corporation. All rights reserved. */ + +#ifndef PLATFORM_POSIX_FUZZ_H +#define PLATFORM_POSIX_FUZZ_H + +#include + +/** + * @brief Reset IPC staging state at the start of a new fuzz testcase. + * + * Must be called before staging new fuzz input so that any leftover + * state from a previous testcase that failed to drain within the tick + * budget cannot influence the new one. + */ +void posix_fuzz_case_begin(void); + +/** + * @brief Query whether any staged IPC fuzz input is still pending. + * + * @return true if the raw input buffer or the IPC staging queue is + * non-empty, false when fully drained. + */ +bool posix_fuzz_case_pending(void); + +/** + * @brief Hard-reset all staged IPC fuzz state. + * + * Called when the simulator tick budget is exhausted before the input + * has been fully drained, ensuring the next testcase starts clean. + */ +void posix_fuzz_case_abort(void); + +#endif /* PLATFORM_POSIX_FUZZ_H */ diff --git a/src/platform/posix/include/platform/lib/memory.h b/src/platform/posix/include/platform/lib/memory.h index 7782ab55048b..f2f6e567223b 100644 --- a/src/platform/posix/include/platform/lib/memory.h +++ b/src/platform/posix/include/platform/lib/memory.h @@ -47,11 +47,6 @@ extern uint32_t posix_trace[]; #define host_to_local(addr) (addr) -static inline void *platform_shared_get(void *ptr, int bytes) -{ - return ptr; -} - #define SHARED_DATA /**/ #endif /* PLATFORM_HOST_PLATFORM_MEMORY_H */ diff --git a/src/platform/posix/ipc.c b/src/platform/posix/ipc.c index 35e203c2d468..8ef28ec654e4 100644 --- a/src/platform/posix/ipc.c +++ b/src/platform/posix/ipc.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: BSD-3-Clause // Copyright(c) 2022 Google LLC. All rights reserved. // Author: Andy Ross +#include #include #include #include @@ -8,12 +9,15 @@ #include #include #include +#include +#include // 6c8f0d53-ff77-4ca1-b825-c0c4e1b0d322 SOF_DEFINE_REG_UUID(ipc_task_posix); static struct ipc *global_ipc; +#ifdef CONFIG_ARCH_POSIX_LIBFUZZER // Not an ISR, called from the native_posix fuzz interrupt. Left // alone for general hygiene. This is how a IPC interrupt would look // if we had one. @@ -33,16 +37,48 @@ extern size_t posix_fuzz_sz; static uint8_t fuzz_in[65536]; static size_t fuzz_in_sz; -// The protocol here is super simple: the first byte is a message size -// in units of 16 bits (the buffer maximum defaults to 384 bytes, and -// I didn't want to waste space early in the buffer lest I confuse the -// fuzzing heuristics). We then copy that much of the input buffer -// (subject to clamping obviously) into the incoming IPC message -// buffer and invoke the ISR. Any remainder will be delivered -// synchronously as another message after receipt of "complete_cmd()" -// from the SOF engine, etc... Eventually we'll receive another fuzz -// input after some amount of simulated time has passed (c.f. -// CONFIG_ZEPHYR_POSIX_FUZZ_TICKS) +/* + * Testcase-isolation helpers used by the libFuzzer entry point in + * fuzz.c. They keep ownership of the cross-call state in one module + * so a new testcase never observes leftovers from a previous one that + * failed to drain inside the simulator tick budget. + */ +void posix_fuzz_case_begin(void) +{ + fuzz_in_sz = 0; +} + +bool posix_fuzz_case_pending(void) +{ + return posix_fuzz_sz != 0 || fuzz_in_sz != 0; +} + +void posix_fuzz_case_abort(void) +{ + posix_fuzz_sz = 0; + fuzz_in_sz = 0; +} + +// The framing protocol is super simple: the first two bytes are a +// little-endian message size in bytes (capped below at +// SOF_IPC_MSG_MAX_SIZE - 4 so it always fits in the IPC message +// buffer). We then copy that much of the input buffer (subject to +// clamping obviously) into the incoming IPC message buffer and invoke +// the ISR. Any remainder will be delivered synchronously as another +// message after receipt of "complete_cmd()" from the SOF engine, +// etc... Eventually we'll receive another fuzz input after some +// amount of simulated time has passed (c.f. +// CONFIG_ZEPHYR_POSIX_FUZZ_TICKS). +// +// The historical encoding used a single byte multiplied by 2, which +// capped each message at 510 bytes. That was fine for IPC3 +// (SOF_IPC_MSG_MAX_SIZE = 384) but limited IPC4 (max 4096) to ~12% +// of its envelope, hiding all large_config / vendor_config / +// pipeline-state-data paths from coverage feedback. Widening to two +// bytes lets the fuzzer reach the full IPC4 message range; existing +// corpora are reinterpreted but libFuzzer recovers within minutes. +#define POSIX_FUZZ_HDR_LEN 2 + static void fuzz_isr(const void *arg) { size_t rem, i, n = MIN(posix_fuzz_sz, sizeof(fuzz_in) - fuzz_in_sz); @@ -50,20 +86,22 @@ static void fuzz_isr(const void *arg) for (i = 0; i < n; i++) fuzz_in[fuzz_in_sz++] = posix_fuzz_buf[i]; - if (fuzz_in_sz == 0) + if (fuzz_in_sz < POSIX_FUZZ_HDR_LEN) return; if (!global_ipc->comp_data) return; - size_t maxsz = SOF_IPC_MSG_MAX_SIZE - 4, msgsz = fuzz_in[0] * 2; + size_t maxsz = SOF_IPC_MSG_MAX_SIZE - 4; + size_t msgsz = (size_t)fuzz_in[0] | ((size_t)fuzz_in[1] << 8); - n = MIN(msgsz, MIN(fuzz_in_sz - 1, maxsz)); - rem = fuzz_in_sz - (n + 1); + msgsz = MIN(msgsz, maxsz); + n = MIN(msgsz, fuzz_in_sz - POSIX_FUZZ_HDR_LEN); + rem = fuzz_in_sz - (n + POSIX_FUZZ_HDR_LEN); memset(global_ipc->comp_data, 0, maxsz); - memcpy(global_ipc->comp_data, &fuzz_in[1], n); - memmove(&fuzz_in[0], &fuzz_in[n + 1], rem); + memcpy(global_ipc->comp_data, &fuzz_in[POSIX_FUZZ_HDR_LEN], n); + memmove(&fuzz_in[0], &fuzz_in[n + POSIX_FUZZ_HDR_LEN], rem); fuzz_in_sz = rem; #ifdef CONFIG_IPC_MAJOR_3 @@ -132,6 +170,7 @@ static void fuzz_isr(const void *arg) posix_ipc_isr(NULL); } +#endif // This API is... confounded by its history. With IPC3, the job of // this function is to get a newly-received IPC message header (!) @@ -200,12 +239,14 @@ int ipc_platform_compact_read_msg(struct ipc_cmd_hdr *hdr, int words) // Re-raise the interrupt if there's still fuzz data to process void ipc_platform_complete_cmd(struct ipc *ipc) { +#ifdef CONFIG_ARCH_POSIX_LIBFUZZER extern void posix_sw_set_pending_IRQ(unsigned int IRQn); if (fuzz_in_sz > 0) { posix_fuzz_sz = 0; posix_sw_set_pending_IRQ(CONFIG_ZEPHYR_POSIX_FUZZ_IRQ); } +#endif } int ipc_platform_send_msg(const struct ipc_msg *msg) @@ -228,8 +269,10 @@ void ipc_platform_send_msg_direct(const struct ipc_msg *msg) int platform_ipc_init(struct ipc *ipc) { +#ifdef CONFIG_ARCH_POSIX_LIBFUZZER IRQ_CONNECT(CONFIG_ZEPHYR_POSIX_FUZZ_IRQ, 0, fuzz_isr, NULL, 0); irq_enable(CONFIG_ZEPHYR_POSIX_FUZZ_IRQ); +#endif global_ipc = ipc; schedule_task_init_edf(&ipc->ipc_task, SOF_UUID(ipc_task_posix_uuid), diff --git a/src/platform/qemu_xtensa/include/platform/lib/memory.h b/src/platform/qemu_xtensa/include/platform/lib/memory.h index d0843904f563..7c8f9da9c1c1 100644 --- a/src/platform/qemu_xtensa/include/platform/lib/memory.h +++ b/src/platform/qemu_xtensa/include/platform/lib/memory.h @@ -8,11 +8,6 @@ /* Dummy memory header for qemu_xtensa */ -static inline void *platform_shared_get(void *ptr, int bytes) -{ - return ptr; -} - #define PLATFORM_DCACHE_ALIGN sizeof(void *) #define HOST_PAGE_SIZE 4096 #define SHARED_DATA diff --git a/src/samples/audio/detect_test.c b/src/samples/audio/detect_test.c index 1e2b21ee9d9e..7979d7db3d6c 100644 --- a/src/samples/audio/detect_test.c +++ b/src/samples/audio/detect_test.c @@ -1113,8 +1113,7 @@ static SHARED_DATA struct comp_driver_info comp_keyword_info = { UT_STATIC void sys_comp_keyword_init(void) { - comp_register(platform_shared_get(&comp_keyword_info, - sizeof(comp_keyword_info))); + comp_register(&comp_keyword_info); } DECLARE_MODULE(sys_comp_keyword_init); diff --git a/src/samples/audio/smart_amp_test_ipc3.c b/src/samples/audio/smart_amp_test_ipc3.c index 3e69bb5f308e..1172fb3c0716 100644 --- a/src/samples/audio/smart_amp_test_ipc3.c +++ b/src/samples/audio/smart_amp_test_ipc3.c @@ -518,6 +518,16 @@ static int smart_amp_prepare(struct comp_dev *dev) sad->in_channels = audio_stream_get_channels(&sad->source_buf->stream); + /* out_channels bounds the processing loop that indexes the + * PLATFORM_MAX_CHANNELS-sized channel map, so reject a larger count + */ + if (sad->out_channels > PLATFORM_MAX_CHANNELS || + sad->in_channels > PLATFORM_MAX_CHANNELS) { + comp_err(dev, "invalid channel count, in %u out %u", + sad->in_channels, sad->out_channels); + return -EINVAL; + } + if (sad->feedback_buf) { audio_stream_set_channels(&sad->feedback_buf->stream, sad->config.feedback_channels); @@ -555,8 +565,7 @@ static SHARED_DATA struct comp_driver_info comp_smart_amp_info = { UT_STATIC void sys_comp_smart_amp_test_init(void) { - comp_register(platform_shared_get(&comp_smart_amp_info, - sizeof(comp_smart_amp_info))); + comp_register(&comp_smart_amp_info); } DECLARE_MODULE(sys_comp_smart_amp_test_init); diff --git a/src/schedule/ll_schedule_xtos.c b/src/schedule/ll_schedule_xtos.c index 10615045d4d2..be24f3b34fd4 100644 --- a/src/schedule/ll_schedule_xtos.c +++ b/src/schedule/ll_schedule_xtos.c @@ -724,6 +724,7 @@ static int reschedule_ll_task(void *data, struct task *task, uint64_t start) } #endif +#if CONFIG_SOF_BOOT_TEST_STANDALONE || CONFIG_LIBRARY static void scheduler_free_ll(void *data, uint32_t flags) { struct ll_schedule_data *sch = data; @@ -739,6 +740,7 @@ static void scheduler_free_ll(void *data, uint32_t flags) irq_local_enable(irq_flags); } +#endif static void ll_scheduler_recalculate_tasks(struct ll_schedule_data *sch, struct clock_notify_data *clk_data) @@ -807,7 +809,8 @@ static const struct scheduler_ops schedule_ll_ops = { .schedule_task_free = schedule_ll_task_free, .schedule_task_cancel = schedule_ll_task_cancel, .reschedule_task = reschedule_ll_task, +#if CONFIG_SOF_BOOT_TEST_STANDALONE || CONFIG_LIBRARY .scheduler_free = scheduler_free_ll, - .scheduler_restore = NULL, +#endif .schedule_task_running = NULL, }; diff --git a/src/schedule/schedule.c b/src/schedule/schedule.c index 5e56b6c490e0..519b8f144ba6 100644 --- a/src/schedule/schedule.c +++ b/src/schedule/schedule.c @@ -22,16 +22,53 @@ SOF_DEFINE_REG_UUID(schedule); DECLARE_TR_CTX(sch_tr, SOF_UUID(schedule_uuid), LOG_LEVEL_INFO); +static inline bool scheduler_is_user(int type) +{ + /* + * currently only LL managed in user-space, but longterm + * goal is to move all audio application level scheduling + * to user-space and only keep Zephyr scheduler logic in + * kernel + */ + return type == SOF_SCHEDULE_LL_TIMER || type == SOF_SCHEDULE_DP; +} + int schedule_task_init(struct task *task, const struct sof_uuid_entry *uid, uint16_t type, uint16_t priority, enum task_state (*run)(void *data), void *data, uint16_t core, uint32_t flags) { + struct schedulers *schedulers; + struct schedule_data *sch = NULL; + struct list_item *slist; + if (type >= SOF_SCHEDULE_COUNT) { tr_err(&sch_tr, "invalid task type"); return -EINVAL; } + if (IS_ENABLED(CONFIG_SOF_USERSPACE_LL) && scheduler_is_user(type)) + schedulers = *arch_user_schedulers_get_for_core(core); + else + schedulers = *arch_schedulers_get(); + + if (!schedulers) + return -ENODEV; + + task->sch = NULL; + list_for_item(slist, &schedulers->list) { + sch = container_of(slist, struct schedule_data, list); + if (type == sch->type) { + task->sch = sch; + break; + } + } + + if (!task->sch) { + tr_err(&sch_tr, "unable to bind scheduler to task %p (type %d)", task, type); + return -ENODEV; + } + task->uid = uid; task->type = type; task->priority = priority; @@ -44,37 +81,52 @@ int schedule_task_init(struct task *task, return 0; } -static void scheduler_register(struct schedule_data *scheduler) +__cold static void scheduler_register(struct schedule_data *scheduler) { struct schedulers **sch = arch_schedulers_get(); + struct k_heap *heap = NULL; + + assert_can_be_cold(); + + if (IS_ENABLED(CONFIG_SOF_USERSPACE_LL) && scheduler_is_user(scheduler->type)) { + sch = arch_user_schedulers_get(); + heap = sof_sys_user_heap_get(); + } if (!*sch) { /* init schedulers list */ - *sch = rzalloc(SOF_MEM_FLAG_KERNEL, - sizeof(**sch)); + *sch = sof_heap_alloc(heap, 0, sizeof(**sch), 0); if (!*sch) { tr_err(&sch_tr, "allocation failed"); return; } + memset(*sch, 0, sizeof(**sch)); list_init(&(*sch)->list); } list_item_append(&scheduler->list, &(*sch)->list); } -void scheduler_init(int type, const struct scheduler_ops *ops, void *data) +__cold void scheduler_init(int type, const struct scheduler_ops *ops, void *data) { struct schedule_data *sch; + struct k_heap *heap = NULL; + + assert_can_be_cold(); + + if (IS_ENABLED(CONFIG_SOF_USERSPACE_LL) && scheduler_is_user(type)) + heap = sof_sys_user_heap_get(); if (!ops || !ops->schedule_task || !ops->schedule_task_cancel || !ops->schedule_task_free) return; - sch = rzalloc(SOF_MEM_FLAG_KERNEL, sizeof(*sch)); + sch = sof_heap_alloc(heap, SOF_MEM_FLAG_KERNEL, sizeof(*sch), 0); if (!sch) { tr_err(&sch_tr, "allocation failed"); sof_panic(SOF_IPC_PANIC_IPC); } + memset(sch, 0, sizeof(*sch)); list_init(&sch->list); sch->type = type; sch->ops = ops; diff --git a/src/schedule/zephyr_dma_domain.c b/src/schedule/zephyr_dma_domain.c index 9bb76660ab29..50901ef7b2af 100644 --- a/src/schedule/zephyr_dma_domain.c +++ b/src/schedule/zephyr_dma_domain.c @@ -462,8 +462,7 @@ static int zephyr_dma_domain_register(struct ll_schedule_domain *domain, K_FOREVER); #ifdef CONFIG_SCHED_CPU_MASK - k_thread_cpu_mask_clear(thread); - k_thread_cpu_mask_enable(thread, core); + k_thread_cpu_pin(thread, core); #endif k_thread_name_set(thread, thread_name); diff --git a/src/schedule/zephyr_domain.c b/src/schedule/zephyr_domain.c index ebf5aea8d4d2..681b0c872f58 100644 --- a/src/schedule/zephyr_domain.c +++ b/src/schedule/zephyr_domain.c @@ -126,7 +126,8 @@ static void zephyr_domain_thread_fn(void *p1, void *p2, void *p3) } #endif - dt->handler(dt->arg); + if (dt->handler) + dt->handler(dt->arg); #ifdef CONFIG_SCHEDULE_LL_STATS_LOG cycles1 = k_cycle_get_32(); @@ -217,8 +218,7 @@ static int zephyr_domain_register(struct ll_schedule_domain *domain, NULL, CONFIG_LL_THREAD_PRIORITY, 0, K_FOREVER); #ifdef CONFIG_SCHED_CPU_MASK - k_thread_cpu_mask_clear(thread); - k_thread_cpu_mask_enable(thread, core); + k_thread_cpu_pin(thread, core); #endif k_thread_name_set(thread, thread_name); @@ -289,63 +289,72 @@ static int zephyr_domain_unregister(struct ll_schedule_domain *domain, #else /* CONFIG_SOF_USERSPACE_LL */ -/* User-space implementation for register/unregister */ - -static int zephyr_domain_register_user(struct ll_schedule_domain *domain, - struct task *task, - void (*handler)(void *arg), void *arg) +/* + * Privileged thread initialization for userspace LL scheduling. + * Creates the scheduling thread, sets up timer, grants access to kernel + * objects. Must be called from kernel context before any user-space + * domain_register() calls. + */ +static int zephyr_domain_thread_init(struct ll_schedule_domain *domain, + struct task *task) { struct zephyr_domain *zephyr_domain = ll_sch_domain_get_pdata(domain); - int core = cpu_get_id(); - struct zephyr_domain_thread *dt = zephyr_domain->domain_thread + core; + struct zephyr_domain_thread *dt; char thread_name[] = "ll_thread0"; k_tid_t thread; + int core = task->core; tr_dbg(&ll_tr, "entry"); - /* domain work only needs registered once on each core */ - if (dt->handler) + if (core >= CONFIG_CORE_COUNT) + return -EINVAL; + + dt = zephyr_domain->domain_thread + core; + + /* thread only needs to be created once per core */ + if (dt->ll_thread) return 0; - __ASSERT_NO_MSG(task->core == core); + dt->handler = NULL; + dt->arg = NULL; - dt->handler = handler; - dt->arg = arg; + dt->sem = k_object_alloc(K_OBJ_SEM); + if (!dt->sem) { + tr_err(&ll_tr, "Failed to allocate semaphore for core %d", core); + return -ENOMEM; + } /* 10 is rather random, we better not accumulate 10 missed timer interrupts */ k_sem_init(dt->sem, 0, 10); thread_name[sizeof(thread_name) - 2] = '0' + core; + /* Allocate thread structure dynamically */ + dt->ll_thread = k_object_alloc(K_OBJ_THREAD); if (!dt->ll_thread) { - /* Allocate thread structure dynamically */ - dt->ll_thread = k_object_alloc(K_OBJ_THREAD); - if (!dt->ll_thread) { - tr_err(&ll_tr, "Failed to allocate thread object for core %d", core); - dt->handler = NULL; - dt->arg = NULL; - return -ENOMEM; - } + tr_err(&ll_tr, "Failed to allocate thread object for core %d", core); + k_object_free(dt->sem); + dt->sem = NULL; + return -ENOMEM; + } - thread = k_thread_create(dt->ll_thread, ll_sched_stack[core], ZEPHYR_LL_STACK_SIZE, - zephyr_domain_thread_fn, zephyr_domain, - INT_TO_POINTER(core), NULL, CONFIG_LL_THREAD_PRIORITY, - K_USER, K_FOREVER); + thread = k_thread_create(dt->ll_thread, ll_sched_stack[core], ZEPHYR_LL_STACK_SIZE, + zephyr_domain_thread_fn, zephyr_domain, + INT_TO_POINTER(core), NULL, CONFIG_LL_THREAD_PRIORITY, + K_USER, K_FOREVER); #ifdef CONFIG_SCHED_CPU_MASK - k_thread_cpu_mask_clear(thread); - k_thread_cpu_mask_enable(thread, core); + k_thread_cpu_pin(thread, core); #endif - k_thread_name_set(thread, thread_name); + k_thread_name_set(thread, thread_name); - k_mem_domain_add_thread(zephyr_ll_mem_domain(), thread); - k_thread_access_grant(thread, dt->sem, domain->lock, zephyr_domain->timer); - user_grant_dai_access_all(thread); - user_grant_dma_access_all(thread); - tr_dbg(&ll_tr, "granted LL access to thread %p (core %d)", thread, core); + k_mem_domain_add_thread(zephyr_ll_mem_domain(), thread); + k_thread_access_grant(thread, dt->sem, domain->lock, zephyr_domain->timer); + user_grant_dai_access_all(thread); + user_grant_dma_access_all(thread); + tr_dbg(&ll_tr, "granted LL access to thread %p (core %d)", thread, core); - k_thread_start(thread); - } + k_thread_start(thread); k_mutex_lock(domain->lock, K_FOREVER); if (!k_timer_user_data_get(zephyr_domain->timer)) { @@ -368,6 +377,43 @@ static int zephyr_domain_register_user(struct ll_schedule_domain *domain, return 0; } +/* + * User-space register: bookkeeping only. The privileged thread setup has + * already been done by domain_thread_init() called from kernel context. + */ +static int zephyr_domain_register_user(struct ll_schedule_domain *domain, + struct task *task, + void (*handler)(void *arg), void *arg) +{ + struct zephyr_domain *zephyr_domain = ll_sch_domain_get_pdata(domain); + struct zephyr_domain_thread *dt; + int core; + + tr_dbg(&ll_tr, "register_user entry"); + + if (task->core >= CONFIG_CORE_COUNT) + return -EINVAL; + + core = task->core; + dt = zephyr_domain->domain_thread + core; + + if (!dt->ll_thread) { + tr_err(&ll_tr, "domain_thread_init() not called for core %d", core); + return -EINVAL; + } + + __ASSERT_NO_MSG(!dt->handler || dt->handler == handler); + if (dt->handler) + return 0; + + dt->handler = handler; + dt->arg = arg; + + tr_info(&ll_tr, "task registered on core %d", core); + + return 0; +} + static int zephyr_domain_unregister_user(struct ll_schedule_domain *domain, struct task *task, uint32_t num_tasks) { @@ -382,30 +428,63 @@ static int zephyr_domain_unregister_user(struct ll_schedule_domain *domain, k_mutex_lock(domain->lock, K_FOREVER); - if (!atomic_read(&domain->total_num_tasks)) { - /* Disable the watchdog */ + zephyr_domain->domain_thread[core].handler = NULL; + + k_mutex_unlock(domain->lock); + + /* + * In this user thread implementation, the timer is left + * running until privileged domain_thread_free() is called + * to clean up resources. + */ + + tr_dbg(&ll_tr, "exit"); + + return 0; +} + +/* + * Free resources acquired by zephyr_domain_thread_init(). + * Stops the timer, aborts the scheduling thread and frees the thread object. + * Must be called from kernel context. + */ +static void zephyr_domain_thread_free(struct ll_schedule_domain *domain, + uint32_t num_tasks) +{ + struct zephyr_domain *zephyr_domain = ll_sch_domain_get_pdata(domain); + int core = cpu_get_id(); + struct zephyr_domain_thread *dt = zephyr_domain->domain_thread + core; + + tr_dbg(&ll_tr, "thread_free entry, core %d, num_tasks %u", core, num_tasks); + + /* Still tasks on other cores, only clean up this core's thread */ + k_mutex_lock(domain->lock, K_FOREVER); + + if (!num_tasks && !atomic_read(&domain->total_num_tasks)) { + /* Last task globally: stop the timer and watchdog */ watchdog_disable(core); k_timer_stop(zephyr_domain->timer); k_timer_user_data_set(zephyr_domain->timer, NULL); } - zephyr_domain->domain_thread[core].handler = NULL; + dt->handler = NULL; + dt->arg = NULL; k_mutex_unlock(domain->lock); - tr_info(&ll_tr, "domain->type %d domain->clk %d", - domain->type, domain->clk); - - /* Thread not removed here, only the timer is stopped. - * Thread object cleanup would require k_thread_abort() which cannot - * be safely called from this context. The thread remains allocated - * but dormant until next registration or system shutdown. - */ + if (dt->ll_thread) { + k_thread_abort(dt->ll_thread); + k_object_free(dt->ll_thread); + dt->ll_thread = NULL; + } - tr_dbg(&ll_tr, "exit"); + if (dt->sem) { + k_object_free(dt->sem); + dt->sem = NULL; + } - return 0; + tr_info(&ll_tr, "thread_free done, core %d", core); } struct k_thread *zephyr_domain_thread_tid(struct ll_schedule_domain *domain) @@ -450,6 +529,8 @@ APP_TASK_DATA static const struct ll_schedule_domain_ops zephyr_domain_ops = { #ifdef CONFIG_SOF_USERSPACE_LL .domain_register = zephyr_domain_register_user, .domain_unregister = zephyr_domain_unregister_user, + .domain_thread_init = zephyr_domain_thread_init, + .domain_thread_free = zephyr_domain_thread_free, #else .domain_register = zephyr_domain_register, .domain_unregister = zephyr_domain_unregister, @@ -460,11 +541,11 @@ APP_TASK_DATA static const struct ll_schedule_domain_ops zephyr_domain_ops = { #endif }; +/* Core 0 only */ struct ll_schedule_domain *zephyr_domain_init(int clk) { struct ll_schedule_domain *domain; struct zephyr_domain *zephyr_domain; - struct zephyr_domain_thread *dt; int core; domain = domain_init(SOF_SCHEDULE_LL_TIMER, clk, false, @@ -514,14 +595,9 @@ struct ll_schedule_domain *zephyr_domain_init(int clk) ll_sch_domain_set_pdata(domain, zephyr_domain); for (core = 0; core < CONFIG_CORE_COUNT; core++) { - dt = zephyr_domain->domain_thread + core; -#ifdef CONFIG_SOF_USERSPACE_LL - dt->sem = k_object_alloc(K_OBJ_SEM); - if (!dt->sem) { - tr_err(&ll_tr, "Failed to allocate semaphore for core %d", core); - k_panic(); - } -#else +#ifndef CONFIG_SOF_USERSPACE_LL + struct zephyr_domain_thread *dt = zephyr_domain->domain_thread + core; + /* not allocated dynamically when LL in kernel space */ dt->sem = &dt->sem_obj; #endif @@ -533,7 +609,8 @@ struct ll_schedule_domain *zephyr_domain_init(int clk) /* Check if currently running in the LL scheduler thread context */ bool ll_sch_is_current(void) { - struct zephyr_domain *zephyr_domain = ll_sch_domain_get_pdata(zephyr_ll_domain()); + struct ll_schedule_domain *ll_dom = zephyr_ll_domain(); + struct zephyr_domain *zephyr_domain = ll_dom ? ll_sch_domain_get_pdata(ll_dom) : NULL; if (!zephyr_domain) return false; diff --git a/src/schedule/zephyr_dp_schedule.c b/src/schedule/zephyr_dp_schedule.c index fdb10c7201af..25fa8b319457 100644 --- a/src/schedule/zephyr_dp_schedule.c +++ b/src/schedule/zephyr_dp_schedule.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -254,7 +255,7 @@ static int scheduler_dp_task_stop(void *data, struct task *task) struct task_dp_pdata *pdata = task->priv_data; /* this is asyn cancel - mark the task as canceled and remove it from scheduling */ - lock_key = scheduler_dp_lock(cpu_get_id()); + lock_key = scheduler_dp_lock(task->core); task->state = SOF_TASK_STATE_CANCEL; list_item_del(&task->list); @@ -306,7 +307,7 @@ static int scheduler_dp_task_shedule(void *data, struct task *task, uint64_t sta struct task_dp_pdata *pdata = task->priv_data; unsigned int lock_key; - lock_key = scheduler_dp_lock(cpu_get_id()); + lock_key = scheduler_dp_lock(task->core); if (task_is_active(task)) { scheduler_dp_unlock(lock_key); @@ -346,14 +347,18 @@ static struct scheduler_ops schedule_dp_ops = { .schedule_task_free = scheduler_dp_task_free, }; -int scheduler_dp_init(void) +__cold int scheduler_dp_init(void) { int ret; + + assert_can_be_cold(); + struct scheduler_dp_data *dp_sch = rzalloc(SOF_MEM_FLAG_KERNEL, sizeof(struct scheduler_dp_data)); if (!dp_sch) return -ENOMEM; + dp_sch->ll_tick_src.priv_data = NULL; list_init(&dp_sch->tasks); scheduler_init(SOF_SCHEDULE_DP, &schedule_dp_ops, dp_sch); diff --git a/src/schedule/zephyr_dp_schedule_application.c b/src/schedule/zephyr_dp_schedule_application.c index 918f13188671..791e16ab190d 100644 --- a/src/schedule/zephyr_dp_schedule_application.c +++ b/src/schedule/zephyr_dp_schedule_application.c @@ -22,6 +22,7 @@ #include #include +#include #include "zephyr_dp_schedule.h" @@ -410,6 +411,20 @@ void scheduler_dp_internal_free(struct task *task) mod_free(pdata->mod, container_of(task, struct scheduler_dp_task_memory, task)); } +#ifdef CONFIG_THREAD_NAME +static void scheduler_dp_thread_name_set(k_tid_t thread_id, struct processing_module *mod) +{ + char name[CONFIG_THREAD_MAX_NAME_LEN]; + + snprintf(name, sizeof(name), "DP:%#x", mod->dev->ipc_config.id); + + k_thread_name_set(thread_id, name); +} +#else +/* k_thread_name_set() is a no-op so skip constructing a thread name */ +#define scheduler_dp_thread_name_set(x, y) +#endif + /* Called only in IPC context */ int scheduler_dp_task_init(struct task **task, const struct sof_uuid_entry *uid, const struct task_ops *ops, struct processing_module *mod, @@ -493,6 +508,7 @@ int scheduler_dp_task_init(struct task **task, const struct sof_uuid_entry *uid, pdata->thread_id = k_thread_create(pdata->thread, p_stack, stack_size, dp_thread_fn, ptask, NULL, NULL, CONFIG_DP_THREAD_PRIORITY, ptask->flags, K_FOREVER); + scheduler_dp_thread_name_set(pdata->thread_id, mod); unsigned int pidx; size_t size; diff --git a/src/schedule/zephyr_ll.c b/src/schedule/zephyr_ll.c index 575a82d91dda..cf5ae0229d27 100644 --- a/src/schedule/zephyr_ll.c +++ b/src/schedule/zephyr_ll.c @@ -9,12 +9,14 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include #include @@ -40,13 +42,60 @@ struct zephyr_ll { struct zephyr_ll_pdata { bool run; bool freeing; - struct k_sem sem; + struct sys_sem sem; }; +#if CONFIG_SOF_USERSPACE_LL +/* + * Mutex pointer in user-accessible partition so user-space threads + * can read the pointer for syscalls. The actual lock object resides + * in kernel memory, there are just handles! + */ +static APP_SYSUSER_BSS struct k_mutex *zephyr_ll_locks[CONFIG_CORE_COUNT]; + +/* + * Shadow ownership tracking for the per-core LL mutex, used only to + * power user_ll_assert_locked(). The kernel k_mutex object is not + * readable from user-space threads, so its owner cannot be inspected + * directly; instead record the owner and recursion depth here, in a + * user-accessible partition. Updated on every lock/unlock of the LL + * mutex, by both the LL thread (zephyr_ll_lock()) and IPC handlers + * (user_ll_lock_sched()). Only ever mutated by the current lock holder + * while the lock is held, so no extra synchronization is required. + */ +#ifdef CONFIG_ASSERT +static APP_SYSUSER_BSS k_tid_t zephyr_ll_lock_owner[CONFIG_CORE_COUNT]; +static APP_SYSUSER_BSS uint32_t zephyr_ll_lock_depth[CONFIG_CORE_COUNT]; + +static inline void zephyr_ll_lock_acquired(int core) +{ + zephyr_ll_lock_owner[core] = k_current_get(); + zephyr_ll_lock_depth[core]++; +} + +static inline void zephyr_ll_lock_releasing(int core) +{ + assert(zephyr_ll_lock_owner[core] == k_current_get()); + if (--zephyr_ll_lock_depth[core] == 0) + zephyr_ll_lock_owner[core] = NULL; +} + +void user_ll_assert_locked(int core) +{ + assert(core < CONFIG_CORE_COUNT && + zephyr_ll_lock_owner[core] == k_current_get()); +} +#else +static inline void zephyr_ll_lock_acquired(int core) { (void)core; } +static inline void zephyr_ll_lock_releasing(int core) { (void)core; } +#endif /* CONFIG_ASSERT */ +#endif + static void zephyr_ll_lock(struct zephyr_ll *sch, uint32_t *flags) { #if CONFIG_SOF_USERSPACE_LL k_mutex_lock(sch->lock, K_FOREVER); + zephyr_ll_lock_acquired(sch->core); #else irq_local_disable(*flags); #endif @@ -55,6 +104,7 @@ static void zephyr_ll_lock(struct zephyr_ll *sch, uint32_t *flags) static void zephyr_ll_unlock(struct zephyr_ll *sch, uint32_t *flags) { #if CONFIG_SOF_USERSPACE_LL + zephyr_ll_lock_releasing(sch->core); k_mutex_unlock(sch->lock); #else irq_local_enable(*flags); @@ -87,7 +137,7 @@ static void zephyr_ll_task_done(struct zephyr_ll *sch, * zephyr_ll_task_free() is trying to free this task. Complete * it and signal the semaphore to let the function proceed */ - k_sem_give(&pdata->sem); + sys_sem_give(&pdata->sem); tr_info(&ll_tr, "task complete %p %pU", task, task->uid); tr_info(&ll_tr, "num_tasks %d total_num_tasks %ld", @@ -221,7 +271,15 @@ static void zephyr_ll_run(void *data) list_item_del(list); list_item_append(list, &task_head); + /* in user-space LL builds, the LL lock protects LL + * tasks against IPC thread, so the lock must be held + * while running the tasks. + * in kernel LL builds, IPC thread blocks interrupts for + * critical section, so lock can be freed here. + */ +#ifndef CONFIG_SOF_USERSPACE_LL zephyr_ll_unlock(sch, &flags); +#endif /* * task's .run() should only return either @@ -236,7 +294,9 @@ static void zephyr_ll_run(void *data) state = SOF_TASK_STATE_RESCHEDULE; } +#ifndef CONFIG_SOF_USERSPACE_LL zephyr_ll_lock(sch, &flags); +#endif if (pdata->freeing || state == SOF_TASK_STATE_COMPLETED) { zephyr_ll_task_done(sch, task); @@ -360,17 +420,7 @@ static int zephyr_ll_task_schedule_common(struct zephyr_ll *sch, struct task *ta ret = domain_register(sch->ll_domain, task, &schedule_ll_callback, sch); if (ret < 0) - tr_err(&ll_tr, "cannot register domain %d", - ret); - -#if CONFIG_SOF_USERSPACE_LL - k_thread_access_grant(zephyr_domain_thread_tid(sch->ll_domain), sch->lock); - - tr_dbg(&ll_tr, "granting access to lock %p for thread %p", sch->lock, - zephyr_domain_thread_tid(sch->ll_domain)); - tr_dbg(&ll_tr, "granting access to domain lock %p for thread %p", &sch->ll_domain->lock, - zephyr_domain_thread_tid(sch->ll_domain)); -#endif + tr_err(&ll_tr, "cannot register domain %d", ret); return 0; } @@ -412,10 +462,12 @@ static int zephyr_ll_task_free(void *data, struct task *task) zephyr_ll_assert_core(sch); +#ifndef CONFIG_SOF_USERSPACE_LL if (k_is_in_isr()) { tr_err(&ll_tr, "cannot free tasks from interrupt context!"); return -EDEADLK; } +#endif zephyr_ll_lock(sch, &flags); @@ -454,7 +506,7 @@ static int zephyr_ll_task_free(void *data, struct task *task) if (must_wait) /* Wait for up to 100 periods */ - k_sem_take(&pdata->sem, K_USEC(LL_TIMER_PERIOD_US * 100)); + sys_sem_take(&pdata->sem, K_USEC(LL_TIMER_PERIOD_US * 100)); /* Protect against racing with schedule_task() */ zephyr_ll_lock(sch, &flags); @@ -495,21 +547,57 @@ static int zephyr_ll_task_cancel(void *data, struct task *task) return 0; } -/* - * Runs on secondary cores in their shutdown sequence. In theory tasks can still - * be active, but other schedulers ignore them too... And we don't need to free - * the scheduler data - it's allocated in the SYS zone. - */ +#if CONFIG_SOF_BOOT_TEST_STANDALONE || CONFIG_LIBRARY static void zephyr_ll_scheduler_free(void *data, uint32_t flags) { struct zephyr_ll *sch = data; zephyr_ll_assert_core(sch); +#if CONFIG_SOF_USERSPACE_LL + zephyr_ll_locks[sch->core] = NULL; + k_object_free(sch->lock); +#endif + if (sch->n_tasks) tr_err(&ll_tr, "%u tasks are still active!", sch->n_tasks); + +#if CONFIG_SOF_USERSPACE_LL + domain_thread_free(sch->ll_domain, sch->n_tasks); +#endif + sof_heap_free(sch->heap, sch); +} +#endif + +#if CONFIG_SOF_USERSPACE_LL +struct k_thread *zephyr_ll_init_context(void *data, struct task *task) +{ + struct zephyr_ll *sch = data; + int ret; + + /* + * Use domain_thread_init() for privileged setup (thread creation, + * timer, access grants). domain_register() is now bookkeeping only + * and will be called later from user context when scheduling tasks. + */ + ret = domain_thread_init(sch->ll_domain, task); + if (ret < 0) { + tr_err(&ll_tr, "cannot init_context %d", ret); + return NULL; + } + + assert(!k_is_user_context()); + k_thread_access_grant(zephyr_domain_thread_tid(sch->ll_domain), sch->lock); + + tr_dbg(&ll_tr, "granting access to lock %p for thread %p", sch->lock, + zephyr_domain_thread_tid(sch->ll_domain)); + tr_dbg(&ll_tr, "granting access to domain lock %p for thread %p", &sch->ll_domain->lock, + zephyr_domain_thread_tid(sch->ll_domain)); + + return zephyr_domain_thread_tid(sch->ll_domain); } +#endif static const struct scheduler_ops zephyr_ll_ops = { .schedule_task = zephyr_ll_task_schedule, @@ -517,15 +605,53 @@ static const struct scheduler_ops zephyr_ll_ops = { .schedule_task_after = zephyr_ll_task_schedule_after, .schedule_task_free = zephyr_ll_task_free, .schedule_task_cancel = zephyr_ll_task_cancel, +#if CONFIG_SOF_BOOT_TEST_STANDALONE || CONFIG_LIBRARY .scheduler_free = zephyr_ll_scheduler_free, +#endif +#if CONFIG_SOF_USERSPACE_LL + .scheduler_init_context = zephyr_ll_init_context, +#endif }; #if CONFIG_SOF_USERSPACE_LL struct task *zephyr_ll_task_alloc(void) { - return sof_heap_alloc(zephyr_ll_user_heap(), SOF_MEM_FLAG_USER, - sizeof(struct task), sizeof(void *)); + struct task *task = sof_heap_alloc(zephyr_ll_user_heap(), SOF_MEM_FLAG_USER, + sizeof(*task), sizeof(void *)); + + if (task) + /* At least .priv_data must be NULL for zephyr_ll_task_init() */ + memset(task, 0, sizeof(*task)); + + return task; +} + +/** + * Lock the LL scheduler to prevent it from processing tasks. + * + * Uses the LL scheduler's own k_mutex which is re-entrant, so + * schedule_task() calls within the locked section will not deadlock. + * Must be paired with user_ll_unlock_sched(). + */ +void user_ll_lock_sched(int core) +{ + assert(core >= 0 && core < CONFIG_CORE_COUNT && zephyr_ll_locks[core] != NULL); + int __maybe_unused ret = k_mutex_lock(zephyr_ll_locks[core], K_FOREVER); + assert(!ret); + zephyr_ll_lock_acquired(core); +} + +/** + * Unlock the LL scheduler after a previous user_ll_lock_sched() call. + */ +void user_ll_unlock_sched(int core) +{ + assert(core >= 0 && core < CONFIG_CORE_COUNT && zephyr_ll_locks[core] != NULL); + zephyr_ll_lock_releasing(core); + int __maybe_unused ret = k_mutex_unlock(zephyr_ll_locks[core]); + assert(!ret); } + #endif /* CONFIG_SOF_USERSPACE_LL */ int zephyr_ll_task_init(struct task *task, @@ -543,6 +669,12 @@ int zephyr_ll_task_init(struct task *task, if (task->priv_data) return -EEXIST; + /* + * schedule_task_init() must be run on target core, see + * sof/zephyr/schedule.c:arch_schedulers_get() + */ + assert(cpu_get_id() == core); + ret = schedule_task_init(task, uid, type, priority, run, data, core, flags); if (ret < 0) @@ -560,7 +692,7 @@ int zephyr_ll_task_init(struct task *task, memset(pdata, 0, sizeof(*pdata)); - k_sem_init(&pdata->sem, 0, 1); + sys_sem_init(&pdata->sem, 0, 1); task->priv_data = pdata; @@ -570,16 +702,18 @@ EXPORT_SYMBOL(zephyr_ll_task_init); /* TODO: low-power mode clock support */ /* Runs on each core during initialisation with the same domain argument */ -int zephyr_ll_scheduler_init(struct ll_schedule_domain *domain) +__cold int zephyr_ll_scheduler_init(struct ll_schedule_domain *domain) { struct zephyr_ll *sch; int core = cpu_get_id(); struct k_heap *heap = sof_sys_heap_get(); int flags = SOF_MEM_FLAG_KERNEL | SOF_MEM_FLAG_COHERENT; + assert_can_be_cold(); + #if CONFIG_SOF_USERSPACE_LL heap = zephyr_ll_user_heap(); - flags = SOF_MEM_FLAG_USER; + flags = SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT; #endif tr_dbg(&ll_tr, "init on core %d", core); @@ -606,6 +740,8 @@ int zephyr_ll_scheduler_init(struct ll_schedule_domain *domain) sof_heap_free(sch->heap, sch); return -ENOMEM; } + assert(core < CONFIG_CORE_COUNT && zephyr_ll_locks[core] == NULL); + zephyr_ll_locks[core] = sch->lock; k_mutex_init(sch->lock); tr_dbg(&ll_tr, "ll-scheduler init done, sch %p sch->lock %p", sch, sch->lock); @@ -634,5 +770,5 @@ struct ll_schedule_domain *zephyr_ll_domain(void) { struct zephyr_ll *ll_sch = scheduler_get_data(SOF_SCHEDULE_LL_TIMER); - return ll_sch->ll_domain; + return ll_sch ? ll_sch->ll_domain : NULL; } diff --git a/src/schedule/zephyr_ll_user.c b/src/schedule/zephyr_ll_user.c index aa33807b4aa3..bad2f6c9cbb6 100644 --- a/src/schedule/zephyr_ll_user.c +++ b/src/schedule/zephyr_ll_user.c @@ -17,25 +17,51 @@ LOG_MODULE_DECLARE(ll_schedule, CONFIG_SOF_LOG_LEVEL); * * This structure encapsulates the memory management resources required for the * low-latency (LL) scheduler in userspace mode. It provides memory isolation - * and heap management for LL scheduler threads. + * and heap management for LL scheduler threads. Only kernel accessible. */ struct zephyr_ll_mem_resources { struct k_mem_domain mem_domain; /**< Memory domain for LL thread isolation */ - struct k_heap *heap; /**< Heap allocator for LL scheduler memory */ + struct k_heap heap; /**< Heap allocator for LL scheduler memory */ }; static struct zephyr_ll_mem_resources ll_mem_resources; -static struct k_heap *zephyr_ll_heap_init(void) +/** + * Heap allocator for LL scheduler memory (user accessible pointer) + * + * Note: this is also user-writable, so kernel must not rely on this to + * be correct and must always validate it separately. + */ +APP_SYSUSER_DATA static struct k_heap *zephyr_ll_heap; + +static struct k_heap *ll_heap_alloc(void) { - struct k_heap *heap = module_driver_heap_init(); - struct k_mem_partition mem_partition; - int ret; + const size_t alloc_size = CONFIG_SOF_ZEPHYR_SYS_USER_HEAP_SIZE; + + BUILD_ASSERT(CONFIG_SOF_ZEPHYR_SYS_USER_HEAP_SIZE % CONFIG_MM_DRV_PAGE_SIZE == 0); + + void *mem = rballoc_align(SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, alloc_size, + CONFIG_MM_DRV_PAGE_SIZE); + if (!mem) + return NULL; + + k_heap_init(&ll_mem_resources.heap, mem, alloc_size); /* - * TODO: the size of LL heap should be independently configurable and - * not tied to CONFIG_SOF_ZEPHYR_USERSPACE_MODULE_HEAP_SIZE + * k_heap_init() does not set these, so set the values + * manually here */ + ll_mem_resources.heap.heap.init_mem = mem; + ll_mem_resources.heap.heap.init_bytes = alloc_size; + + return &ll_mem_resources.heap; +} + +static void ll_heap_init(void) +{ + struct k_heap *heap = ll_heap_alloc(); + struct k_mem_partition mem_partition; + int ret; if (!heap) { tr_err(&ll_tr, "heap alloc fail"); @@ -53,6 +79,7 @@ static struct k_heap *zephyr_ll_heap_init(void) if (ret) k_panic(); +#ifdef CONFIG_CACHE_HAS_MIRRORED_MEMORY_REGIONS mem_partition.start = (uintptr_t)sys_cache_uncached_ptr_get(heap->heap.init_mem); mem_partition.attr = K_MEM_PARTITION_P_RW_U_RW; ret = k_mem_domain_add_partition(&ll_mem_resources.mem_domain, &mem_partition); @@ -60,25 +87,59 @@ static struct k_heap *zephyr_ll_heap_init(void) (void *)mem_partition.start, heap->heap.init_bytes, ret); if (ret) k_panic(); - - return heap; +#endif } void zephyr_ll_user_resources_init(void) { + int ret; + k_mem_domain_init(&ll_mem_resources.mem_domain, 0, NULL); - ll_mem_resources.heap = zephyr_ll_heap_init(); + ll_heap_init(); + + /* store a user-accessible pointer */ + zephyr_ll_heap = &ll_mem_resources.heap; /* attach common partition to LL domain */ user_memory_attach_common_partition(zephyr_ll_mem_domain()); + + ret = user_memory_attach_system_user_partition(zephyr_ll_mem_domain()); + if (ret) + k_panic(); +} + +/** + * Check if 'heap' is a valid heap pointer. + * + * Available only in kernel mode. + * + * @return true if valid + */ +bool zephyr_ll_user_heap_verify(struct k_heap *heap) +{ + return heap == &ll_mem_resources.heap; } +/** + * Returns heap object to use in user-space LL code. + * + * Can be called from user-space. + * + * @return heap pointer that can be passed to sof_heap_alloc() + */ struct k_heap *zephyr_ll_user_heap(void) { - return ll_mem_resources.heap; + return zephyr_ll_heap; } +/** + * Returns pointer to LL user-space memory domain. + * + * Available only in kernel mode. + * + * @return pointer to memory domain + */ struct k_mem_domain *zephyr_ll_mem_domain(void) { return &ll_mem_resources.mem_domain; diff --git a/src/schedule/zephyr_twb_schedule.c b/src/schedule/zephyr_twb_schedule.c index aee61360d697..5e85cbf61134 100644 --- a/src/schedule/zephyr_twb_schedule.c +++ b/src/schedule/zephyr_twb_schedule.c @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -348,12 +349,14 @@ static struct scheduler_ops schedule_twb_ops = { .schedule_task_free = scheduler_twb_task_free, }; -int scheduler_twb_init(void) +__cold int scheduler_twb_init(void) { struct scheduler_twb_data *twb_sch = rzalloc(SOF_MEM_FLAG_KERNEL, sizeof(struct scheduler_twb_data)); int ret; + assert_can_be_cold(); + if (!twb_sch) return -ENOMEM; diff --git a/src/trace/dma-trace.c b/src/trace/dma-trace.c index 554204ac5c70..bfa359b4ba74 100644 --- a/src/trace/dma-trace.c +++ b/src/trace/dma-trace.c @@ -30,7 +30,7 @@ #include #ifdef __ZEPHYR__ -#include +#include #endif #include diff --git a/test/cmocka/src/audio/pipeline/pipeline_connection_mocks.c b/test/cmocka/src/audio/pipeline/pipeline_connection_mocks.c index de05f8e3b284..7315ce387562 100644 --- a/test/cmocka/src/audio/pipeline/pipeline_connection_mocks.c +++ b/test/cmocka/src/audio/pipeline/pipeline_connection_mocks.c @@ -49,6 +49,9 @@ struct pipeline_connect_data *get_standard_connect_objects(void) sch->ops = &schedule_mock_ops; list_item_append(&sch->list, &schedulers->list); + /* Bind the scheduler to the task, as schedule_task_init() would */ + pipe->pipe_task->sch = sch; + struct comp_dev *first = calloc(sizeof(struct comp_dev), 1); struct comp_ipc_config *first_comp = &first->ipc_config; diff --git a/test/cmocka/src/audio/volume/volume_process.c b/test/cmocka/src/audio/volume/volume_process.c index 389a4e75a503..b6c1fec45e45 100644 --- a/test/cmocka/src/audio/volume/volume_process.c +++ b/test/cmocka/src/audio/volume/volume_process.c @@ -71,6 +71,9 @@ static int setup(void **state) cd->vol = test_malloc(vol_size); + /* number of channels used by the processing functions */ + cd->channels = vol_state->parameters.channels; + /* set processing function and volume */ #if CONFIG_IPC_MAJOR_4 cd->scale_vol = vol_get_processing_function(vol_state->mod->dev, cd); @@ -266,6 +269,8 @@ static void test_audio_vol(void **state) struct processing_module_test_data *vol_state = *state; struct processing_module *mod = vol_state->mod; struct vol_data *cd = module_get_private_data(mod); + struct cir_buf_source source_buf; + struct cir_buf_sink sink_buf; switch (audio_stream_get_frm_fmt(&vol_state->sinks[0]->stream)) { case SOF_IPC_FRAME_S16_LE: @@ -289,7 +294,15 @@ static void test_audio_vol(void **state) vol_state->input_buffers[0]->consumed = 0; vol_state->output_buffers[0]->size = 0; - cd->scale_vol(mod, vol_state->input_buffers[0], vol_state->output_buffers[0], + source_buf.buf_start = audio_stream_get_addr(&vol_state->sources[0]->stream); + source_buf.buf_end = audio_stream_get_end_addr(&vol_state->sources[0]->stream); + source_buf.ptr = audio_stream_get_rptr(&vol_state->sources[0]->stream); + + sink_buf.buf_start = audio_stream_get_addr(&vol_state->sinks[0]->stream); + sink_buf.buf_end = audio_stream_get_end_addr(&vol_state->sinks[0]->stream); + sink_buf.ptr = audio_stream_get_wptr(&vol_state->sinks[0]->stream); + + cd->scale_vol(mod, &source_buf, &sink_buf, mod->dev->frames, cd->attenuation); vol_state->verify(mod, vol_state->sinks[0], vol_state->sources[0]); diff --git a/test/cmocka/src/common_mocks.c b/test/cmocka/src/common_mocks.c index 16fee8f2ff48..2c2867346293 100644 --- a/test/cmocka/src/common_mocks.c +++ b/test/cmocka/src/common_mocks.c @@ -119,6 +119,11 @@ struct k_heap * WEAK sof_sys_heap_get(void) return NULL; } +struct k_heap * WEAK sof_sys_user_heap_get(void) +{ + return NULL; +} + void WEAK *sof_heap_alloc(struct k_heap *heap, uint32_t flags, size_t bytes, size_t alignment) { diff --git a/test/cmocka/src/math/arithmetic/CMakeLists.txt b/test/cmocka/src/math/arithmetic/CMakeLists.txt index 721c50020ba5..6625aef444ef 100644 --- a/test/cmocka/src/math/arithmetic/CMakeLists.txt +++ b/test/cmocka/src/math/arithmetic/CMakeLists.txt @@ -1,37 +1,5 @@ # SPDX-License-Identifier: BSD-3-Clause -cmocka_test(scalar_power - scalar_power.c - ${PROJECT_SOURCE_DIR}/src/math/power.c -) - -cmocka_test(base2_logarithm - base2_logarithm.c - ${PROJECT_SOURCE_DIR}/src/math/base2log.c -) - -cmocka_test(exponential - exponential.c - ${PROJECT_SOURCE_DIR}/src/math/exp_fcn.c - ${PROJECT_SOURCE_DIR}/src/math/exp_fcn_hifi.c -) -cmocka_test(square_root - square_root.c - ${PROJECT_SOURCE_DIR}/src/math/sqrt_int16.c -) - -cmocka_test(base_10_logarithm - base_10_logarithm.c - ${PROJECT_SOURCE_DIR}/src/math/log_10.c - ${PROJECT_SOURCE_DIR}/src/math/base2log.c -) - -cmocka_test(base_e_logarithm - base_e_logarithm.c - ${PROJECT_SOURCE_DIR}/src/math/log_e.c - ${PROJECT_SOURCE_DIR}/src/math/base2log.c -) - cmocka_test(a_law_codec a_law_codec.c ${PROJECT_SOURCE_DIR}/src/math/a_law.c diff --git a/test/cmocka/src/math/arithmetic/base2_logarithm.c b/test/cmocka/src/math/arithmetic/base2_logarithm.c deleted file mode 100644 index a06e44d09b78..000000000000 --- a/test/cmocka/src/math/arithmetic/base2_logarithm.c +++ /dev/null @@ -1,73 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// -// Copyright(c) 2021 Intel Corporation. All rights reserved. -// -// Author: Shriram Shastry - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include "log2_tables.h" -/* 'Error[max] = 0.0000236785999981,THD(-dBc) = -92.5128795787487235' */ -#define CMP_TOLERANCE 0.0000236785691029 - -/* testvector in Q32.0 */ -static const uint32_t uv[100] = { - 1ULL, 43383509ULL, 86767017ULL, 130150525ULL, 173534033ULL, 216917541ULL, - 260301049ULL, 303684557ULL, 347068065ULL, 390451573ULL, 433835081ULL, 477218589ULL, - 520602097ULL, 563985605ULL, 607369113ULL, 650752621ULL, 694136129ULL, 737519638ULL, - 780903146ULL, 824286654ULL, 867670162ULL, 911053670ULL, 954437178ULL, 997820686ULL, - 1041204194ULL, 1084587702ULL, 1127971210ULL, 1171354718ULL, 1214738226ULL, 1258121734ULL, - 1301505242ULL, 1344888750ULL, 1388272258ULL, 1431655766ULL, 1475039274ULL, 1518422782ULL, - 1561806290ULL, 1605189798ULL, 1648573306ULL, 1691956814ULL, 1735340322ULL, 1778723830ULL, - 1822107338ULL, 1865490846ULL, 1908874354ULL, 1952257862ULL, 1995641370ULL, 2039024878ULL, - 2082408386ULL, 2125791894ULL, 2169175403ULL, 2212558911ULL, 2255942419ULL, 2299325927ULL, - 2342709435ULL, 2386092943ULL, 2429476451ULL, 2472859959ULL, 2516243467ULL, 2559626975ULL, - 2603010483ULL, 2646393991ULL, 2689777499ULL, 2733161007ULL, 2776544515ULL, 2819928023ULL, - 2863311531ULL, 2906695039ULL, 2950078547ULL, 2993462055ULL, 3036845563ULL, 3080229071ULL, - 3123612579ULL, 3166996087ULL, 3210379595ULL, 3253763103ULL, 3297146611ULL, 3340530119ULL, - 3383913627ULL, 3427297135ULL, 3470680643ULL, 3514064151ULL, 3557447659ULL, 3600831168ULL, - 3644214676ULL, 3687598184ULL, 3730981692ULL, 3774365200ULL, 3817748708ULL, 3861132216ULL, - 3904515724ULL, 3947899232ULL, 3991282740ULL, 4034666248ULL, 4078049756ULL, 4121433264ULL, - 4164816772ULL, 4208200280ULL, 4251583788ULL, 4294967295ULL}; - -static void test_math_arithmetic_base2log_fixed(void **state) -{ - (void)state; - - uint32_t u[100]; - int i; - - memcpy_s((void *)&u[0], sizeof(u), (void *)&uv[0], 100U * sizeof(uint32_t)); - for (i = 0; i < ARRAY_SIZE(log2_lookup_table); i++) { - float y = Q_CONVERT_QTOF(base2_logarithm(u[i]), 0); - float diff = fabs(log2_lookup_table[i] - (double)y / (1 << 16)); - - if (diff > CMP_TOLERANCE) { - printf("%s: diff for %.16f: value = %.16f, log2 = %.16f\n", __func__, diff, - (double)u[i], (double)y / (1 << 16)); - assert_true(diff <= CMP_TOLERANCE); - } - } -} - -int main(void) -{ - const struct CMUnitTest tests[] = { - cmocka_unit_test(test_math_arithmetic_base2log_fixed) - }; - - cmocka_set_message_output(CM_OUTPUT_TAP); - - return cmocka_run_group_tests(tests, NULL, NULL); -} diff --git a/test/cmocka/src/math/arithmetic/base_10_logarithm.c b/test/cmocka/src/math/arithmetic/base_10_logarithm.c deleted file mode 100644 index 3973df31a515..000000000000 --- a/test/cmocka/src/math/arithmetic/base_10_logarithm.c +++ /dev/null @@ -1,105 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// -// Copyright(c) 2021 Intel Corporation. All rights reserved. -// -// Author: Shriram Shastry -// -// - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include "log2_tables.h" -/* 'Error[max] = 0.0000071279028671,THD = -102.9407645424143993 ' */ -/* 'Error[max] = rms(log10() - double(log10_int32()))' */ -/* 'THD = 20*log10(Error[max])' */ -#define CMP_TOLERANCE 0.0000071279028671 -/* Natural logarithm log10(X) reference table generated by matlab/Octave */ -/* UQ4.28 */ -static const double common_log10_ref_table[] = { - 0.0000000000000000, 7.6373246662453802, 7.9383546619093615, 8.1144459209650428, - 8.2393846575733427, 8.3362946705813989, 8.4154759166290241, 8.4824227062596371, - 8.5404146532373240, 8.5915671756847054, 8.6373246662453802, 8.6787173514036056, - 8.7165059122930053, 8.7512680193222625, 8.7834527026386606, 8.8134159259684335, - 8.8414446495269665, 8.8677735882125130, 8.8925971719048302, 8.9160782677250818, - 8.9383546624098908, 8.9595439614559940, 8.9797473475226131, 8.9990525026982162, - 9.0175359083740947, 9.0352646753178405, 9.0522980146012202, 9.0686884307751292, - 9.0844826979451199, 9.0997226644895282, 9.1144459212987297, 9.1286863604025754, - 9.1424746448781171, 9.1558386064266184, 9.1688035835820649, 9.1813927108816724, - 9.1936271672907388, 9.2055263908534872, 9.2171082633890631, 9.2283892737852433, - 9.2393846580738721, 9.2501085234534361, 9.2605739571199752, 9.2707931222905753, - 9.2807773431865943, 9.2905371804656394, 9.3000824983621975, 9.3094225246070792, - 9.3185659040380759, 9.3275207466824899, 9.3362946709818218, 9.3448948427358882, - 9.3533280102652014, 9.3616005362239267, 9.3697184264391105, 9.3776873561036460, - 9.3855126936091011, 9.3931995222691196, 9.4007526601535094, 9.4081766782268659, - 9.4154759169627091, 9.4226545015843630, 9.4297163562280168, 9.4366652161756566, - 9.4435046406985137, 9.4502380233502628, 9.4568686022422757, 9.4633994693944423, - 9.4698335793932600, 9.4761737574178788, 9.4824227066886628, 9.4885830153874373, - 9.4946571630937573, 9.5006475267772306, 9.5065563863821918, 9.5123859300375031, - 9.5181382589213257, 9.5238153918078847, 9.5294192693208828, 9.5349517579159713, - 9.5404146536127215, 9.5458096854947918, 9.5511385189953373, 9.5564027589832818, - 9.5616039526647825, 9.5667435923129869, 9.5718231178381536, 9.5768439193242560, - 9.5818073388505756, 9.5867146733402073, 9.5915671761296206, 9.5963660590064990, - 9.6011124940261787, 9.6058076152298781, 9.6104525202710605, 9.6150482719557271, - 9.6195958997020572, 9.6240964009244330, 9.6285507423464711, 9.6329598611462810}; - -/* testvector in Q32.0 */ -static const uint32_t uv[100] = { - 1ULL, 43383509ULL, 86767017ULL, 130150525ULL, 173534033ULL, 216917541ULL, - 260301049ULL, 303684557ULL, 347068065ULL, 390451573ULL, 433835081ULL, 477218589ULL, - 520602097ULL, 563985605ULL, 607369113ULL, 650752621ULL, 694136129ULL, 737519638ULL, - 780903146ULL, 824286654ULL, 867670162ULL, 911053670ULL, 954437178ULL, 997820686ULL, - 1041204194ULL, 1084587702ULL, 1127971210ULL, 1171354718ULL, 1214738226ULL, 1258121734ULL, - 1301505242ULL, 1344888750ULL, 1388272258ULL, 1431655766ULL, 1475039274ULL, 1518422782ULL, - 1561806290ULL, 1605189798ULL, 1648573306ULL, 1691956814ULL, 1735340322ULL, 1778723830ULL, - 1822107338ULL, 1865490846ULL, 1908874354ULL, 1952257862ULL, 1995641370ULL, 2039024878ULL, - 2082408386ULL, 2125791894ULL, 2169175403ULL, 2212558911ULL, 2255942419ULL, 2299325927ULL, - 2342709435ULL, 2386092943ULL, 2429476451ULL, 2472859959ULL, 2516243467ULL, 2559626975ULL, - 2603010483ULL, 2646393991ULL, 2689777499ULL, 2733161007ULL, 2776544515ULL, 2819928023ULL, - 2863311531ULL, 2906695039ULL, 2950078547ULL, 2993462055ULL, 3036845563ULL, 3080229071ULL, - 3123612579ULL, 3166996087ULL, 3210379595ULL, 3253763103ULL, 3297146611ULL, 3340530119ULL, - 3383913627ULL, 3427297135ULL, 3470680643ULL, 3514064151ULL, 3557447659ULL, 3600831168ULL, - 3644214676ULL, 3687598184ULL, 3730981692ULL, 3774365200ULL, 3817748708ULL, 3861132216ULL, - 3904515724ULL, 3947899232ULL, 3991282740ULL, 4034666248ULL, 4078049756ULL, 4121433264ULL, - 4164816772ULL, 4208200280ULL, 4251583788ULL, 4294967295ULL}; - -static void test_math_arithmetic_base10log_fixed(void **state) -{ - (void)state; - - double clogfxp; - double diff; - int i; - - for (i = 0; i < ARRAY_SIZE(common_log10_ref_table); i++) { - clogfxp = log10_int32(uv[i]); - diff = fabs(common_log10_ref_table[i] - (double)clogfxp / (1 << 28)); - - if (diff > CMP_TOLERANCE) { - printf("%s: diff for %.16f: val = %16d, log10() = %.16f\n", __func__, diff, - uv[i], clogfxp / (1 << 28)); - assert_true(diff <= CMP_TOLERANCE); - } - } -} - -int main(void) -{ - const struct CMUnitTest tests[] = { - cmocka_unit_test(test_math_arithmetic_base10log_fixed) - }; - - cmocka_set_message_output(CM_OUTPUT_TAP); - - return cmocka_run_group_tests(tests, NULL, NULL); -} diff --git a/test/cmocka/src/math/arithmetic/base_e_logarithm.c b/test/cmocka/src/math/arithmetic/base_e_logarithm.c deleted file mode 100644 index c2c1d2083c8b..000000000000 --- a/test/cmocka/src/math/arithmetic/base_e_logarithm.c +++ /dev/null @@ -1,104 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// -// Copyright(c) 2021 Intel Corporation. All rights reserved. -// -// Author: Shriram Shastry - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include "log2_tables.h" -/* 'Error[max] = 0.0000164133276926,THD(-dBc) = -95.6960671942683234' */ -/* 'Error[max] = rms(log() - double(log_int32()))' */ -/* 'THD = 20*log10(Error[max])' */ -#define CMP_TOLERANCE 0.0000164133276926 - -/* Natural logarithm loge(X) reference table generated by matlab/Octave */ -/* UQ5.27 */ -static const double natural_log_lookup_table[] = { - 0.0000000000000000, 17.5855899268523359, 18.2787371074122831, 18.6842022155204468, - 18.9718842879722267, 19.1950278392864391, 19.3773493960803940, 19.5315000759076511, - 19.6650314685321739, 19.7828145041885577, 19.8881750198463827, 19.9834851996507084, - 20.0704965766403376, 20.1505392860869676, 20.2246472581140395, 20.2936401294912301, - 20.3581786505327571, 20.4188032722644479, 20.4759616860290699, 20.5300289072319480, - 20.5813222015588408, 20.6301123656733907, 20.6766323812583899, 20.7210841437836706, - 20.7636437581607112, 20.8044657526425461, 20.8436864657603671, 20.8814267937103786, - 20.9177944378507625, 20.9528857576336485, 20.9867873092828354, 21.0195771320810394, - 21.0513258303723845, 21.0820974890173112, 21.1119504521464485, 21.1409379890003279, - 21.1691088659487328, 21.1965078407425196, 21.2231760877918916, 21.2491515741640455, - 21.2744693821187845, 21.2991619946810467, 21.3232595462333343, 21.3467900436180038, - 21.3697795618183370, 21.3922524176471107, 21.4142313243436178, 21.4357375295432568, - 21.4567909387206548, 21.4774102259037889, 21.4976129332024932, 21.5174155604805932, - 21.5368336463203107, 21.5558818412742781, 21.5745739742703257, 21.5929231129229997, - 21.6109416184107097, 21.6286411954956677, 21.6460329381935921, 21.6631273715394208, - 21.6799344898427790, 21.6964637917813938, 21.7127243130127638, 21.7287246543415016, - 21.7444730112924880, 21.7599771978118319, 21.7752446699265008, 21.7902825472754031, - 21.8050976330453672, 21.8196964324517815, 21.8340851698895619, 21.8482698048676056, - 21.8622560468288185, 21.8760493689479993, 21.8896550209909755, 21.9030780413106569, - 21.9163232680485471, 21.9293953496040821, 21.9422987544284780, 21.9550377801946830, - 21.9676165623906030, 21.9800390823784895, 21.9923091749598925, 22.0044305354820757, - 22.0164067265188734, 22.0282411841561903, 22.0399372239099236, 22.0514980465667030, - 22.0629267423782807, 22.0742262976204415, 22.0853995982070579, 22.0964494343823858, - 22.1073785049035614, 22.1181894209970018, 22.1288847101032040, 22.1394668194234150, - 22.1499381192805984, 22.1603009063062437, 22.1705574064637361, 22.1807097776854185}; - -/* testvector in Q32.0 */ -static const uint32_t uv[100] = { - 1ULL, 43383509ULL, 86767017ULL, 130150525ULL, 173534033ULL, 216917541ULL, - 260301049ULL, 303684557ULL, 347068065ULL, 390451573ULL, 433835081ULL, 477218589ULL, - 520602097ULL, 563985605ULL, 607369113ULL, 650752621ULL, 694136129ULL, 737519638ULL, - 780903146ULL, 824286654ULL, 867670162ULL, 911053670ULL, 954437178ULL, 997820686ULL, - 1041204194ULL, 1084587702ULL, 1127971210ULL, 1171354718ULL, 1214738226ULL, 1258121734ULL, - 1301505242ULL, 1344888750ULL, 1388272258ULL, 1431655766ULL, 1475039274ULL, 1518422782ULL, - 1561806290ULL, 1605189798ULL, 1648573306ULL, 1691956814ULL, 1735340322ULL, 1778723830ULL, - 1822107338ULL, 1865490846ULL, 1908874354ULL, 1952257862ULL, 1995641370ULL, 2039024878ULL, - 2082408386ULL, 2125791894ULL, 2169175403ULL, 2212558911ULL, 2255942419ULL, 2299325927ULL, - 2342709435ULL, 2386092943ULL, 2429476451ULL, 2472859959ULL, 2516243467ULL, 2559626975ULL, - 2603010483ULL, 2646393991ULL, 2689777499ULL, 2733161007ULL, 2776544515ULL, 2819928023ULL, - 2863311531ULL, 2906695039ULL, 2950078547ULL, 2993462055ULL, 3036845563ULL, 3080229071ULL, - 3123612579ULL, 3166996087ULL, 3210379595ULL, 3253763103ULL, 3297146611ULL, 3340530119ULL, - 3383913627ULL, 3427297135ULL, 3470680643ULL, 3514064151ULL, 3557447659ULL, 3600831168ULL, - 3644214676ULL, 3687598184ULL, 3730981692ULL, 3774365200ULL, 3817748708ULL, 3861132216ULL, - 3904515724ULL, 3947899232ULL, 3991282740ULL, 4034666248ULL, 4078049756ULL, 4121433264ULL, - 4164816772ULL, 4208200280ULL, 4251583788ULL, 4294967295ULL}; - -static void test_math_arithmetic_base_e_log_fixed(void **state) -{ - (void)state; - - double logefxp; - double diff; - int i; - - for (i = 0; i < ARRAY_SIZE(natural_log_lookup_table); i++) { - logefxp = ln_int32(uv[i]); - diff = fabs(natural_log_lookup_table[i] - (double)logefxp / (1 << 27)); - - if (diff > CMP_TOLERANCE) { - printf("%s: diff for %.16f: value = %16d, log() = %.16f\n", __func__, diff, - uv[i], logefxp / (1 << 27)); - assert_true(diff <= CMP_TOLERANCE); - } - } -} - -int main(void) -{ - const struct CMUnitTest tests[] = { - cmocka_unit_test(test_math_arithmetic_base_e_log_fixed) - }; - - cmocka_set_message_output(CM_OUTPUT_TAP); - - return cmocka_run_group_tests(tests, NULL, NULL); -} diff --git a/test/cmocka/src/math/arithmetic/exponential.c b/test/cmocka/src/math/arithmetic/exponential.c deleted file mode 100644 index 3e44bf0c3743..000000000000 --- a/test/cmocka/src/math/arithmetic/exponential.c +++ /dev/null @@ -1,291 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -/* - * Copyright(c) 2022-2025 Intel Corporation. - * - * Author: Shriram Shastry - * Seppo Ingalsuo - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#define ULP_TOLERANCE 1.0 -#define ULP_SCALE 1.9073e-06 /* For exp() output Q13.19, 1 / 2^19 */ -#define NUMTESTSAMPLES 256 - -#define NUMTESTSAMPLES_TEST2 100 -#define ABS_DELTA_TOLERANCE_TEST2 2.0e-6 -#define REL_DELTA_TOLERANCE_TEST2 1000.0 /* rel. error is large with values near zero */ -#define NUMTESTSAMPLES_TEST3 100 -#define ABS_DELTA_TOLERANCE_TEST3 2.0e-6 -#define REL_DELTA_TOLERANCE_TEST3 10.0e-2 -#define SOFM_EXP_FIXED_ARG_MIN -11.5 -#define SOFM_EXP_FIXED_ARG_MAX 7.6245 - -#define NUMTESTSAMPLES_TEST4 100 -#define ABS_DELTA_TOLERANCE_TEST4 2.5e-5 -#define REL_DELTA_TOLERANCE_TEST4 1000.0 /* rel. error is large with values near zero */ - -/** - * Saturates input to 32 bits - * @param x Input value - * @return Saturated output value - */ -static int32_t saturate32(int64_t x) -{ - if (x < INT32_MIN) - return INT32_MIN; - else if (x > INT32_MAX) - return INT32_MAX; - - return x; -} - -/** - * Generates linearly spaced values for a vector with end points and number points in - * desired fractional Q-format for 32 bit integer. If the test values exceed int32_t - * range, the values are saturated to INT32_MIN to INT32_MAX range. - * - * @param a First value of test vector - * @param b Last value of test vector - * @param step_count Number of values in vector - * @param point Calculate n-th point of vector 0 .. step_count - 1 - * @param qformat Number of fractional bits y in Qx.y format - * @param fout Pointer to calculated test vector value, double - * @param iout Pointer to calculated test vector value, int32_t - */ -static void gen_testvector_linspace_int32(double a, double b, int step_count, int point, - int qformat, double *fout, int32_t *iout) -{ - double fstep = (b - a) / (step_count - 1); - double fvalue = a + fstep * point; - int64_t itmp; - - itmp = (int64_t)round(fvalue * (double)(1 << qformat)); - *iout = saturate32(itmp); - *fout = (double)*iout / (1 << qformat); - return; -} - -/** - * Test for sofm_exp_approx() - * @param state Cmocka internal state - */ -static void test_function_sofm_exp_approx(void **state) -{ - (void)state; - - int32_t accum; - int i; - double a_i; - double max_ulp = 0; - double ulp; - double a_tmp = -8; - double b_tmp = 8; - int32_t b_i; - - for (i = 0; i < NUMTESTSAMPLES; i++) { - gen_testvector_linspace_int32(a_tmp, b_tmp, NUMTESTSAMPLES, i, 28, &a_i, &b_i); - accum = sofm_exp_approx(b_i); - ulp = fabs(exp(a_i) - (double)accum / (1 << 19)) / ULP_SCALE; - if (ulp > max_ulp) - max_ulp = ulp; - - if (ulp > ULP_TOLERANCE) { - printf("%s: ULP for %.16f: value = %.16f, Exp = %.16f\n", __func__, - ulp, (double)b_i / (1 << 28), (double)accum / (1 << 19)); - assert_true(false); - } - } - printf("%s: Worst-case ULP: %g ULP_SCALE %g\n", __func__, max_ulp, ULP_SCALE); -} - - -/** - * Calculate reference exponent value - * @param x Input value - * @param qformat Fractional bits y in Qx.y format - * @return Saturated exponent value to match fractional format - */ -static double ref_exp(double x, int qformat) -{ - double yf; - int64_t yi; - - yf = exp(x); - yi = yf * (1 << qformat); - if (yi > INT32_MAX) - yi = INT32_MAX; - else if (yi < INT32_MIN) - yi = INT32_MIN; - - yf = (double)yi / (1 << qformat); - return yf; -} - -/** - * Calculates test exponent function and compares result to reference exponent. - * @param ivalue Fractional format input value Q5.27 - * @param iexp_value Fractional format output value Q12.20 - * @param abs_delta_max Calculated absolute error - * @param rel_delta_max Calculated relative error - * @param abs_delta_tolerance Tolerance for absolute error - * @param rel_delta_tolerance Tolerance for relative error - */ -static void test_exp_with_input_value(int32_t ivalue, int32_t *iexp_value, - double *abs_delta_max, double *rel_delta_max, - double abs_delta_tolerance, double rel_delta_tolerance) -{ - double fvalue, fexp_value, ref_exp_value; - double rel_delta, abs_delta; - double eps = 1e-9; - - *iexp_value = sofm_exp_fixed(ivalue); - fvalue = (double)ivalue / (1 << 27); /* Q5.27 */ - fexp_value = (double)*iexp_value / (1 << 20); /* Q12.20 */ - ref_exp_value = ref_exp(fvalue, 20); - abs_delta = fabs(ref_exp_value - fexp_value); - rel_delta = abs_delta / (ref_exp_value + eps); - if (abs_delta > *abs_delta_max) - *abs_delta_max = abs_delta; - - if (rel_delta > *rel_delta_max) - *rel_delta_max = rel_delta; - - if (abs_delta > abs_delta_tolerance) { - printf("%s: Absolute error %g exceeds limit %g, input %g output %g.\n", - __func__, abs_delta, abs_delta_tolerance, fvalue, fexp_value); - assert_true(false); - } - - if (rel_delta > rel_delta_tolerance) { - printf("%s: Relative error %g exceeds limit %g, input %g output %g.\n", - __func__, rel_delta, rel_delta_tolerance, fvalue, fexp_value); - assert_true(false); - } -} - -/** - * Test for sofm_exp_fixed() - * @param state Cmocka internal state - */ -static void test_function_sofm_exp_fixed(void **state) -{ - (void)state; - double rel_delta_max, abs_delta_max; - double tmp; - int32_t ivalue, iexp_value; - int i; - - /* Test max int32_t range with coarse grid */ - rel_delta_max = 0; - abs_delta_max = 0; - for (i = 0; i < NUMTESTSAMPLES_TEST2; i++) { - gen_testvector_linspace_int32(-16, 16, NUMTESTSAMPLES_TEST2, i, 27, &tmp, &ivalue); - test_exp_with_input_value(ivalue, &iexp_value, &abs_delta_max, &rel_delta_max, - ABS_DELTA_TOLERANCE_TEST2, REL_DELTA_TOLERANCE_TEST2); - } - - printf("%s: Absolute max error was %.6e (max range).\n", __func__, abs_delta_max); - printf("%s: Relative max error was %.6e (max range).\n", __func__, rel_delta_max); - - /* Test max int32_t middle range with fine grid */ - rel_delta_max = 0; - abs_delta_max = 0; - for (i = 0; i < NUMTESTSAMPLES_TEST3; i++) { - gen_testvector_linspace_int32(SOFM_EXP_FIXED_ARG_MIN, SOFM_EXP_FIXED_ARG_MAX, - NUMTESTSAMPLES_TEST3, i, 27, &tmp, &ivalue); - test_exp_with_input_value(ivalue, &iexp_value, &abs_delta_max, &rel_delta_max, - ABS_DELTA_TOLERANCE_TEST3, REL_DELTA_TOLERANCE_TEST3); - } - - printf("%s: Absolute max error was %.6e (middle).\n", __func__, abs_delta_max); - printf("%s: Relative max error was %.6e (middle).\n", __func__, rel_delta_max); -} - -/** - * Reference function for dB to linear conversion - * @param x Input value - * @param qformat Fractional bits y in Qx.y format for saturation - * @return Saturated linear value - */ -static double ref_db2lin(double x, int qformat) -{ - double fref; - int64_t iref; - - fref = pow(10, x / 20); - iref = fref * (1 << qformat); - return (double)saturate32(iref) / (1 << qformat); -} - -/** - * Test for sofm_db2lin_fixed() - * @param state Cmocka internal state - */ -static void test_function_sofm_db2lin_fixed(void **state) -{ - (void)state; - double abs_delta, rel_delta, abs_delta_max, rel_delta_max; - double fin, fout, fref; - double eps = 1e-9; - int32_t iin, iout; - int i; - - rel_delta_max = 0; - abs_delta_max = 0; - for (i = 0; i < NUMTESTSAMPLES_TEST2; i++) { - gen_testvector_linspace_int32(-128, 128, NUMTESTSAMPLES_TEST4, i, 24, &fin, &iin); - iout = sofm_db2lin_fixed(iin); - fout = (double)iout / (1 << 20); - fref = ref_db2lin(fin, 20); - abs_delta = fabs(fref - fout); - rel_delta = abs_delta / (fref + eps); - if (abs_delta > abs_delta_max) - abs_delta_max = abs_delta; - - if (rel_delta > rel_delta_max) - rel_delta_max = rel_delta; - - if (abs_delta > ABS_DELTA_TOLERANCE_TEST4) { - printf("%s: Absolute error %g exceeds limit %g, input %g output %g.\n", - __func__, abs_delta, ABS_DELTA_TOLERANCE_TEST4, fin, fout); - assert_true(false); - } - - if (rel_delta > REL_DELTA_TOLERANCE_TEST4) { - printf("%s: Relative error %g exceeds limit %g, input %g output %g.\n", - __func__, rel_delta, REL_DELTA_TOLERANCE_TEST4, fin, fout); - assert_true(false); - } - } - printf("%s: Absolute max error was %.6e.\n", __func__, abs_delta_max); - printf("%s: Relative max error was %.6e.\n", __func__, rel_delta_max); -} - -int main(void) -{ - const struct CMUnitTest tests[] = { - cmocka_unit_test(test_function_sofm_exp_approx), - cmocka_unit_test(test_function_sofm_exp_fixed), - cmocka_unit_test(test_function_sofm_db2lin_fixed), - }; - - cmocka_set_message_output(CM_OUTPUT_TAP); - - return cmocka_run_group_tests(tests, NULL, NULL); -} diff --git a/test/cmocka/src/math/arithmetic/scalar_power.c b/test/cmocka/src/math/arithmetic/scalar_power.c deleted file mode 100644 index 8b23791e979e..000000000000 --- a/test/cmocka/src/math/arithmetic/scalar_power.c +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// -// Copyright(c) 2021 Intel Corporation. All rights reserved. -// -// Author: Shriram Shastry - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include "power_tables.h" -/* 'Error (max = 0.000034912111005), THD+N = -96.457180359025074' */ -#define CMP_TOLERANCE 0.0000150363575813 - -static void test_math_arithmetic_power_fixed(void **state) -{ - (void)state; - - double p; - int i; - int j; - - for (i = 0; i < ARRAY_SIZE(b); i++) { - for (j = 0; j < ARRAY_SIZE(e); j++) { - p = power_int32(b[i], e[j]); - float diff = fabsf(power_table[i][j] - (double)p / (1 << 15)); - - if (diff > CMP_TOLERANCE) { - printf("%s: diff for %.16f: base = %.16f", - __func__, diff, (double)b[i] / (1 << 25)); - printf(", exponent = %.16f, power = %.16f\n", - (double)e[j] / (1 << 29), (double)p / (1 << 15)); - assert_true(diff <= CMP_TOLERANCE); - } - } - } -} - -int main(void) -{ - const struct CMUnitTest tests[] = { - cmocka_unit_test(test_math_arithmetic_power_fixed) - }; - - cmocka_set_message_output(CM_OUTPUT_TAP); - - return cmocka_run_group_tests(tests, NULL, NULL); -} diff --git a/test/cmocka/src/math/arithmetic/square_root.c b/test/cmocka/src/math/arithmetic/square_root.c deleted file mode 100644 index 6a31588c1235..000000000000 --- a/test/cmocka/src/math/arithmetic/square_root.c +++ /dev/null @@ -1,162 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// -// Copyright(c) 2021 Intel Corporation. All rights reserved. -// -// Author: Shriram Shastry - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -/* 'Error[max] = 0.0003000860000000,THD(-dBc) = -87.1210823527511309' */ -#define CMP_TOLERANCE 0.0001689942 - -static const double sqrt_ref_table[] = { - 0.0000000000000000, 0.2529127196287289, 0.3580137261684250, 0.4383362543470480, - 0.5060667106469264, 0.5657458434447398, 0.6199010757774179, 0.6695089151758922, - 0.7156864056624241, 0.7590598625931949, 0.8002380017063674, 0.8392530626247365, - 0.8765332548597343, 0.9124250825410271, 0.9468285879714448, 0.9800251112854200, - 1.0121334212938529, 1.0433710015497843, 1.0735864616438677, 1.1029744939820685, - 1.1317074351394887, 1.1596234572049671, 1.1868830634270588, 1.2135304899342250, - 1.2397036881347898, 1.2652391387105444, 1.2902693214499832, 1.3148230929007141, - 1.3390178303517843, 1.3626935069009465, 1.3859648038460428, 1.4089384024417106, - 1.4314580907679415, 1.4536289448738284, 1.4754666899408471, 1.4970674458754356, - 1.5182805344369004, 1.5392012945030940, 1.5598414883410430, 1.5802893574042065, - 1.6003997303408295, 1.6202605162827983, 1.6399552204252408, 1.6593426315110451, - 1.6785061252494731, 1.6974532854396907, 1.7162624043615824, 1.7347972458979175, - 1.7531361407845656, 1.7712851751976586, 1.7893183496097054, 1.8071040368501201, - 1.8247163735084968, 1.8422265952170487, 1.8595062978852748, 1.8766268983537990, - 1.8935927121149891, 1.9104717594746068, 1.9271396388170734, 1.9436645881555799, - 1.9601125007572908, 1.9763617734046062, 1.9924785326635266, 2.0084659685628234, - 2.0243874459327196, 2.0401248429936829, 2.0557417684986605, 2.0712409474756917, - 2.0866835042418388, 2.1019545405705138, 2.1171154277400652, 2.1322257663648099, - 2.1471729232877355, 2.1620167451363552, 2.1767593459084997, 2.1914584719713490, - 2.2060043241401410, 2.2204548907543695, 2.2348120201987909, 2.2491317769308226, - 2.2633070038662453, 2.2773940013752560, 2.2914476694602910, 2.3053627188850347, - 2.3191942802134968, 2.3329438383992445, 2.3466648541067809, 2.3602543890966499, - 2.3737661268541177, 2.3872013883939496, 2.4006123079591588, 2.4138981537908761, - 2.4271112748749282, 2.4403028756693299, 2.4533737931163282, 2.4663754402969551, - 2.4793089069839604, 2.4922242356226696, 2.5050242482608827, 2.5177591878742098, - 2.5304782774210888, 2.5430857547967194, 2.5556310375326090, 2.5681150370943278, - 2.5805859466650203, 2.5929498012639969, 2.6052549809231724, 2.6175023131556161, - 2.6297390257875399, 2.6418728560436060, 2.6539512111660981, 2.6660206330081166, - 2.6779900782816579, 2.6899062628881700, 2.7017698915479462, 2.7136266381449752, - 2.7253870138018930, 2.7370968595849874, 2.7487568212739375, 2.7604117531402812, - 2.7719736453698474, 2.7834875128828940, 2.7949976241045360, 2.8064170328908711, - 2.8177901636300033, 2.8291175744390689, 2.8404427884354582, 2.8516802201728368, - 2.8628735427669523, 2.8740232715872360, 2.8851722218959477, 2.8962361080806240, - 2.9072578897476569, 2.9182798738083706, 2.9292187124939990, 2.9401168530136688, - 2.9509747462702896, 2.9618340496219568, 2.9726126187665289, 2.9833522462156559, - 2.9940941216626773, 3.0047569707257522, 3.0153821145710538, 3.0259699503836783, - 3.0365610688738007, 3.0470753139280951, 3.0575534030495688, 3.0679957066870220, - 3.0784422425351754, 3.0888139284157279, 3.0991509043809078, 3.1094927741514371, - 3.1197612338526808, 3.1299960063872287, 3.1401974211424988, 3.1504045499149789, - 3.1605400917999757, 3.1706432337342845, 3.1807142844611178, 3.1907918051402224, - 3.2007994606816590, 3.2107759235502562, 3.2207593849316032, 3.2306742112715421, - 3.2405587023112234, 3.2504131347991749, 3.2602752232365293, 3.2700702400713046, - 3.2798360048560355, 3.2895727781126838, 3.2993178153786578, 3.3089972636170311, - 3.3186484800856810, 3.3283083869662677, 3.3379037677111065, 3.3474716438306089, - 3.3570122504989461, 3.3665620793882591, 3.3760487375221642, 3.3855088128485207, - 3.3949784839156196, 3.4043859578490805, 3.4137675072784321, 3.4231233453528955, - 3.4324892457042018, 3.4417941928048226, 3.4510740515635128, 3.4603290238249023, - 3.4695944917958350, 3.4788001927748020, 3.4879815975718680, 3.4971738031409019, - 3.5063070962374359, 3.5154166604934614, 3.5245026799003858, 3.5335998818485379, - 3.5426392659640071, 3.5516556438511886, 3.5606491902811768, 3.5696542746637245, - 3.5786025882144274, 3.5875285822032135, 3.5964663647113397, 3.6053481324623839, - 3.6142080737002402, 3.6230463485511746, 3.6318967259718442, 3.6406920594682268, - 3.6494661959833250, 3.6582526566654741, 3.6669847755001657, 3.6756961500510350, - 3.6843869274616097, 3.6930903069956198, 3.7017402474207994, 3.7103700224000571, - 3.7189797723132347, 3.7276023837381045, 3.7361724230822109, 3.7447228493908598, - 3.7532863204297375, 3.7617978476886553, 3.7702901600042669, 3.7787633869263368, - 3.7872498886065071, 3.7956852559847478, 3.8041019184887777, 3.8125000000000000, - 3.8209115711273665, 3.8292727871131094, 3.8376157861196840, 3.8459724266107265, - 3.8542792776341468, 3.8625682639598748, 3.8708395003538962, 3.8791245690071618, - 3.8873605782876637, 3.8955791750874478, 3.9037804693815712, 3.9119957742180653, - 3.9201627238228260, 3.9283126944020128, 3.9364768015796816, 3.9445930655930783, - 3.9526926641056979, 3.9607756993580185, 3.9688730295891301, 3.9769231786332004, - 3.9849570653270532, 3.9930053589840071, 3.9999694823054588, 3.9999694823054588}; -/* testvector in Q4.12 */ -static const uint32_t uv[252] = { - 0U, 262U, 525U, 787U, 1049U, 1311U, - 1574U, 1836U, 2098U, 2360U, 2623U, 2885U, - 3147U, 3410U, 3672U, 3934U, 4196U, 4459U, - 4721U, 4983U, 5246U, 5508U, 5770U, 6032U, - 6295U, 6557U, 6819U, 7081U, 7344U, 7606U, - 7868U, 8131U, 8393U, 8655U, 8917U, 9180U, - 9442U, 9704U, 9966U, 10229U, 10491U, 10753U, - 11016U, 11278U, 11540U, 11802U, 12065U, 12327U, - 12589U, 12851U, 13114U, 13376U, 13638U, 13901U, - 14163U, 14425U, 14687U, 14950U, 15212U, 15474U, - 15737U, 15999U, 16261U, 16523U, 16786U, 17048U, - 17310U, 17572U, 17835U, 18097U, 18359U, 18622U, - 18884U, 19146U, 19408U, 19671U, 19933U, 20195U, - 20457U, 20720U, 20982U, 21244U, 21507U, 21769U, - 22031U, 22293U, 22556U, 22818U, 23080U, 23342U, - 23605U, 23867U, 24129U, 24392U, 24654U, 24916U, - 25178U, 25441U, 25703U, 25965U, 26228U, 26490U, - 26752U, 27014U, 27277U, 27539U, 27801U, 28063U, - 28326U, 28588U, 28850U, 29113U, 29375U, 29637U, - 29899U, 30162U, 30424U, 30686U, 30948U, 31211U, - 31473U, 31735U, 31998U, 32260U, 32522U, 32784U, - 33047U, 33309U, 33571U, 33833U, 34096U, 34358U, - 34620U, 34883U, 35145U, 35407U, 35669U, 35932U, - 36194U, 36456U, 36719U, 36981U, 37243U, 37505U, - 37768U, 38030U, 38292U, 38554U, 38817U, 39079U, - 39341U, 39604U, 39866U, 40128U, 40390U, 40653U, - 40915U, 41177U, 41439U, 41702U, 41964U, 42226U, - 42489U, 42751U, 43013U, 43275U, 43538U, 43800U, - 44062U, 44324U, 44587U, 44849U, 45111U, 45374U, - 45636U, 45898U, 46160U, 46423U, 46685U, 46947U, - 47210U, 47472U, 47734U, 47996U, 48259U, 48521U, - 48783U, 49045U, 49308U, 49570U, 49832U, 50095U, - 50357U, 50619U, 50881U, 51144U, 51406U, 51668U, - 51930U, 52193U, 52455U, 52717U, 52980U, 53242U, - 53504U, 53766U, 54029U, 54291U, 54553U, 54816U, - 55078U, 55340U, 55602U, 55865U, 56127U, 56389U, - 56651U, 56914U, 57176U, 57438U, 57701U, 57963U, - 58225U, 58487U, 58750U, 59012U, 59274U, 59536U, - 59799U, 60061U, 60323U, 60586U, 60848U, 61110U, - 61372U, 61635U, 61897U, 62159U, 62421U, 62684U, - 62946U, 63208U, 63471U, 63733U, 63995U, 64257U, - 64520U, 64782U, 65044U, 65307U, UINT16_MAX, UINT16_MAX}; -static void test_math_arithmetic_sqrt_fixed(void **state) -{ - (void)state; - - uint32_t u[252]; - int i; - double y; - double diff; - - memcpy_s((void *)&u[0], sizeof(u), (void *)&uv[0], 252U * sizeof(uint32_t)); - for (i = 0; i < ARRAY_SIZE(sqrt_ref_table); i++) { - y = Q_CONVERT_QTOF(sofm_sqrt_int16(u[i]), 12); - diff = fabs(sqrt_ref_table[i] - y); - - if (diff > CMP_TOLERANCE) { - printf("%s: diff for %.16f: reftbl = %.16f, sqrt = %.16f\n", __func__, - diff, (double)sqrt_ref_table[i], y); - assert_true(diff <= CMP_TOLERANCE); - } - } -} - -int main(void) -{ - const struct CMUnitTest tests[] = { - cmocka_unit_test(test_math_arithmetic_sqrt_fixed) - }; - - cmocka_set_message_output(CM_OUTPUT_TAP); - - return cmocka_run_group_tests(tests, NULL, NULL); -} diff --git a/tools/ctl/CMakeLists.txt b/tools/ctl/CMakeLists.txt index 5c4c2dd315b5..ba897c99d126 100644 --- a/tools/ctl/CMakeLists.txt +++ b/tools/ctl/CMakeLists.txt @@ -11,6 +11,16 @@ target_link_libraries(sof-ctl PRIVATE "-lasound" ) +# tools/lib is only needed as a -L link path for a locally built ALSA. +# Do not let CMake also embed it as an ELF RPATH/RUNPATH: it points into +# the build tree and, once stripped to empty on install, leaves an empty +# DT_RUNPATH tag that trips distro ELF security scanners (scanelf reports +# "NULL DT_RUNPATH"). Skip the build RPATH and keep the install RPATH +# empty so no DT_RUNPATH tag is emitted. See thesofproject/sof#10070. +set_target_properties(sof-ctl PROPERTIES + SKIP_BUILD_RPATH TRUE + INSTALL_RPATH "") + target_compile_options(sof-ctl PRIVATE -Wall -Werror ) diff --git a/tools/logger/convert.c b/tools/logger/convert.c index 4e0a6cdc59ae..834ee5a1b243 100644 --- a/tools/logger/convert.c +++ b/tools/logger/convert.c @@ -126,8 +126,14 @@ static const char *format_uid(uint32_t uid_ptr, int use_colors, bool be, bool up const struct sof_uuid_entry *uid_entry; char *str; + /* + * The whole struct sof_uuid_entry is read at uid_ptr, so require that + * many bytes to remain in the uids region; a bare ">=" upper bound + * would accept a pointer whose entry straddles the end of the buffer. + */ if (uid_ptr < uids_dict->base_address || - uid_ptr >= uids_dict->base_address + uids_dict->data_length) { + uid_ptr + sizeof(struct sof_uuid_entry) > + uids_dict->base_address + uids_dict->data_length) { str = calloc(1, strlen(BAD_PTR_STR) + 1 + 6); if (!str) { log_err("can't allocate memory\n"); diff --git a/tools/logger/filter.c b/tools/logger/filter.c index 0943f68d16b2..7a3d53c37246 100644 --- a/tools/logger/filter.c +++ b/tools/logger/filter.c @@ -104,7 +104,7 @@ static char *filter_parse_component_name(char *input_str, struct filter_element */ if (strlen(scan_format_string) == 0) { ret = snprintf(scan_format_string, sizeof(scan_format_string), - "%%%d[^0-9* ]s", UUID_NAME_MAX_LEN); + "%%%d[^0-9* ]s", UUID_NAME_MAX_LEN - 1); if (ret <= 0) return NULL; } diff --git a/tools/plugin/alsaplug/tplg.c b/tools/plugin/alsaplug/tplg.c index 7b05b7ead50a..140a0aac91c6 100644 --- a/tools/plugin/alsaplug/tplg.c +++ b/tools/plugin/alsaplug/tplg.c @@ -999,6 +999,12 @@ static int plug_prepare_widget(snd_sof_plug_t *plug, struct tplg_pcm_info *pcm_i } if (i == pipeline_list->count) { + if (pipeline_list->count >= TPLG_MAX_PCM_PIPELINES) { + SNDERR("error: too many pipelines for PCM, max %d\n", + TPLG_MAX_PCM_PIPELINES); + return -EINVAL; + } + pipeline_list->pipelines[pipeline_list->count] = comp_info->pipe_info; pipeline_list->count++; } diff --git a/tools/plugin/modules/ov_noise_suppression/CMakeLists.txt b/tools/plugin/modules/ov_noise_suppression/CMakeLists.txt index 039e9056ee45..fcf1439d0510 100644 --- a/tools/plugin/modules/ov_noise_suppression/CMakeLists.txt +++ b/tools/plugin/modules/ov_noise_suppression/CMakeLists.txt @@ -39,4 +39,22 @@ set_target_properties(sof_ns INSTALL_RPATH "${sof_install_directory}/alsa-lib" INSTALL_RPATH_USE_LINK_PATH TRUE ) + +# Regression test for the input-shape validation guard. Gated on +# OpenVINO_FOUND alongside the module itself, so a host without +# OpenVINO simply skips it. +add_executable(test_ns_shape_validation test_ns_shape_validation.cpp) +target_link_libraries(test_ns_shape_validation PRIVATE sof_ns_interface) +target_include_directories(test_ns_shape_validation PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${sof_source_directory}/src/include + ${sof_source_directory}/posix/include + ${sof_source_directory}/src/arch/host/include + ${sof_source_directory}/src/platform/posix/include) +set_target_properties(test_ns_shape_validation PROPERTIES LINKER_LANGUAGE CXX) + +add_test(NAME ns_shape_zero + COMMAND test_ns_shape_validation --dim 0 --dim 480) +add_test(NAME ns_shape_overflow + COMMAND test_ns_shape_validation --dim 9223372036854775807 --dim 1024) endif() diff --git a/tools/plugin/modules/ov_noise_suppression/noise_suppression_interface.cpp b/tools/plugin/modules/ov_noise_suppression/noise_suppression_interface.cpp index fa74736d062f..6e8356a7440e 100644 --- a/tools/plugin/modules/ov_noise_suppression/noise_suppression_interface.cpp +++ b/tools/plugin/modules/ov_noise_suppression/noise_suppression_interface.cpp @@ -12,6 +12,7 @@ #include "noise_suppression_interface.h" #define NS_MAX_SOURCE_CHANNELS 2 +#define NS_MAX_SHAPE_DIM (1u << 24) extern "C" { struct ns_data { @@ -85,6 +86,9 @@ extern "C" { nd->infer_request[i] = compiled_model.create_infer_request(); nd->inp_shape = nd->model->input("input").get_shape(); + for (auto dim : nd->inp_shape) + if (!dim || dim > NS_MAX_SHAPE_DIM) + return -EINVAL; return 0; } @@ -141,6 +145,9 @@ extern "C" { ov::Shape state_shape; state_shape = nd->model->input(inp_state_name).get_shape(); + for (auto dim : state_shape) + if (!dim || dim > NS_MAX_SHAPE_DIM) + return -EINVAL; if (nd->iter > 0) { /* * set input state by corresponding output state from prev diff --git a/tools/plugin/modules/ov_noise_suppression/test_ns_shape_validation.cpp b/tools/plugin/modules/ov_noise_suppression/test_ns_shape_validation.cpp new file mode 100644 index 000000000000..35a224581423 --- /dev/null +++ b/tools/plugin/modules/ov_noise_suppression/test_ns_shape_validation.cpp @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 Intel Corporation. All rights reserved. +// +// Author: Pally Mediratta + +#include +#include +#include +#include +#include +#include + +#include "noise_suppression_interface.h" + +static void usage(const char *prog) +{ + std::printf( +"Usage: %s [--model PATH | --dim N [--dim N ...]] [--expect ok|reject]\n" +"\n" +"Regression test for the OpenVINO noise-suppression plugin's input-shape\n" +"validation (commit \"tools: plugin: ov_noise_suppression: validate model\n" +"input shape dimensions\"). Invokes ov_ns_init() against either:\n" +"\n" +" --model PATH a caller-supplied OpenVINO model XML file, or\n" +" --dim N [--dim N ...] a minimal synthesized model XML built from the\n" +" given input port dimensions and written to a\n" +" temporary file.\n" +"\n" +" --expect ok|reject expected outcome (default: reject).\n" +" \"ok\": ov_ns_init() must return 0.\n" +" \"reject\": ov_ns_init() must return non-zero.\n" +"\n" +"Returns 0 on PASS, 1 on FAIL, 2 on usage error.\n" +"\n" +"Examples:\n" +" %s --dim 0 --dim 480 # zero dim, expect reject\n" +" %s --dim 9223372036854775807 --dim 1024 # overflow, expect reject\n" +" %s --model real_model.xml --expect ok # real model, expect accept\n", + prog, prog, prog, prog); +} + +static int write_mock_model(const std::string &path, + const std::vector &dims) +{ + std::ofstream f(path); + if (!f) + return -1; + + f << "" + "" + ""; + for (auto d : dims) + f << "" << d << ""; + f << ""; + return f ? 0 : -1; +} + +int main(int argc, char **argv) +{ + std::string model_path; + std::vector dims; + bool expect_reject = true; + + for (int i = 1; i < argc; i++) { + std::string a = argv[i]; + if (a == "-h" || a == "--help") { + usage(argv[0]); + return 0; + } + if (a == "--model" && i + 1 < argc) { + model_path = argv[++i]; + } else if (a == "--dim" && i + 1 < argc) { + dims.push_back(std::strtoll(argv[++i], nullptr, 0)); + } else if (a == "--expect" && i + 1 < argc) { + std::string v = argv[++i]; + if (v == "ok") { + expect_reject = false; + } else if (v == "reject") { + expect_reject = true; + } else { + std::fprintf(stderr, + "unknown --expect value: %s\n", + v.c_str()); + usage(argv[0]); + return 2; + } + } else { + std::fprintf(stderr, "unknown or incomplete arg: %s\n", + a.c_str()); + usage(argv[0]); + return 2; + } + } + + std::string scratch; + if (model_path.empty()) { + if (dims.empty()) { + std::fprintf(stderr, + "must supply --model PATH or one or more --dim N\n"); + usage(argv[0]); + return 2; + } + + char tmpl[] = "/tmp/ns_shape_test_XXXXXX.xml"; + int fd = mkstemps(tmpl, 4); + if (fd < 0) { + std::perror("mkstemps"); + return 2; + } + close(fd); + scratch = tmpl; + if (write_mock_model(scratch, dims) != 0) { + std::fprintf(stderr, "failed to write mock model %s\n", + scratch.c_str()); + std::remove(scratch.c_str()); + return 2; + } + model_path = scratch; + } + + struct noise_suppression_data *nd = + (struct noise_suppression_data *)std::calloc(1, sizeof(*nd)); + if (!nd) { + std::perror("calloc"); + if (!scratch.empty()) + std::remove(scratch.c_str()); + return 2; + } + + int ret = ov_ns_init(nd, model_path.c_str()); + + if (!scratch.empty()) + std::remove(scratch.c_str()); + + bool rejected = (ret != 0); + bool pass = (rejected == expect_reject); + + std::printf("%s: ov_ns_init() returned %d (%s), expected %s\n", + pass ? "PASS" : "FAIL", + ret, + rejected ? "rejected" : "accepted", + expect_reject ? "reject" : "ok"); + + ov_ns_free(nd); + std::free(nd); + + return pass ? 0 : 1; +} diff --git a/tools/probes/probes_demux.c b/tools/probes/probes_demux.c index a411eef117b9..b92aaf88a19b 100644 --- a/tools/probes/probes_demux.c +++ b/tools/probes/probes_demux.c @@ -23,6 +23,7 @@ #define APP_NAME "sof-probes" #define PACKET_MAX_SIZE 4096 /**< Size limit for probe data packet */ +#define PACKET_DATA_SIZE_MAX (16u * 1024u * 1024u) /**< Sanity limit for packet data size */ #define DATA_READ_LIMIT 4096 /**< Data limit for file read */ #define FILES_LIMIT 32 /**< Maximum num of probe output files */ #define FILE_PATH_LIMIT 128 /**< Path limit for probe output files */ @@ -125,7 +126,17 @@ int init_wave(struct dma_frame_parser *p, uint32_t buffer_id, uint32_t format) p->files[i].header.fmt.subchunk_size = 16; p->files[i].header.fmt.audio_format = 1; p->files[i].header.fmt.num_channels = ((format & PROBE_MASK_NB_CHANNELS) >> PROBE_SHIFT_NB_CHANNELS) + 1; - p->files[i].header.fmt.sample_rate = sample_rate[(format & PROBE_MASK_SAMPLE_RATE) >> PROBE_SHIFT_SAMPLE_RATE]; + /* the sample-rate field is 4 bits (0-15) but the table has fewer + * entries; fall back to a valid default (48000 Hz) for an out-of-range + * index so the read stays in bounds and the WAV header keeps a usable + * rate + */ + uint32_t rate_idx = (format & PROBE_MASK_SAMPLE_RATE) >> PROBE_SHIFT_SAMPLE_RATE; + + if (rate_idx >= sizeof(sample_rate) / sizeof(sample_rate[0])) + p->files[i].header.fmt.sample_rate = 48000; + else + p->files[i].header.fmt.sample_rate = sample_rate[rate_idx]; p->files[i].header.fmt.bits_per_sample = (((format & PROBE_MASK_CONTAINER_SIZE) >> PROBE_SHIFT_CONTAINER_SIZE) + 1) * 8; p->files[i].header.fmt.byte_rate = p->files[i].header.fmt.sample_rate * p->files[i].header.fmt.num_channels * @@ -194,6 +205,12 @@ int process_sync(struct dma_frame_parser *p) { struct probe_data_packet *temp_packet; + if (p->packet->data_size_bytes > PACKET_DATA_SIZE_MAX) { + fprintf(stderr, "error: packet data size %u exceeds maximum %u\n", + p->packet->data_size_bytes, PACKET_DATA_SIZE_MAX); + return -EINVAL; + } + /* request to copy data_size from probe packet and 64-bit checksum */ p->total_data_to_copy = p->packet->data_size_bytes + sizeof(uint64_t); diff --git a/tools/rimage/config/lnl.toml.h b/tools/rimage/config/lnl.toml.h index 7a574d4ae906..faefbb8acadb 100644 --- a/tools/rimage/config/lnl.toml.h +++ b/tools/rimage/config/lnl.toml.h @@ -162,5 +162,9 @@ #include