diff --git a/developer_guides/firmware/module_integration_guide.rst b/developer_guides/firmware/module_integration_guide.rst index 347e3af..fe34952 100644 --- a/developer_guides/firmware/module_integration_guide.rst +++ b/developer_guides/firmware/module_integration_guide.rst @@ -83,7 +83,13 @@ Creating and integrating an audio module follows a structured 10-step developmen Step 1: Directory Structure & File Taxonomy ******************************************* -Audio processing components reside under ``src/audio//`` in the main SOF firmware repository (`thesofproject/sof `_). When integrating third-party libraries (such as TensorFlow Lite Micro or proprietary acoustic libraries), external code is typically referenced via ``modules/audio/`` or vendor submodules while maintaining a thin SOF adapter inside ``src/audio//``. +Audio processing components reside under ``src/audio//`` in the main SOF firmware repository (`thesofproject/sof `_). When integrating third-party libraries (such as TensorFlow Lite Micro, SpeexDSP, or proprietary acoustic echo cancellation libraries), external library source trees are placed in ``modules/audio/`` or integrated via Zephyr west modules, leaving ``src/audio//`` strictly responsible for the thin SOF module adapter wrapper. + +SOF strictly enforces an architectural separation of concerns across files: +* **Lifecycle & Framework Abstraction (``.c``)**: Implements framework callbacks and coordinates with pipeline scheduling. +* **Portable Reference Arithmetic (``-generic.c``)**: Contains platform-independent ISO C99 scalar routines. This code must run identically on host Linux/macOS workstations and all target DSP architectures. +* **Hardware Acceleration (``-hifi4.c``)**: Implements specialized SIMD vector intrinsics (Tensilica HiFi 3/4/5, ARM Helium, RISC-V Vector) to minimize DSP cycle consumption. +* **IPC Parameter Serialization (``-ipc4.c``)**: Isolates host-to-firmware IPC messaging, byte unpacking, and coefficient deserialization. A fully-formed SOF audio component utilizes the following file structure: @@ -127,12 +133,19 @@ A fully-formed SOF audio component utilizes the following file structure: Step 2: Core Headers & Interface Lifecycle ****************************************** -Every audio processing component interfaces with the SOF scheduler and pipeline manager through the **Module Adapter API**. +Every audio processing component interfaces with the SOF scheduler and pipeline manager through the **Module Adapter API**. The module adapter abstracts lower-level DSP threading, ring buffer wrapping, cross-core scheduling, and IPC transport, presenting a clean object-oriented lifecycle interface to component developers. Essential Header Includes ========================= -Include the core module adapter and buffer management headers: +Component implementations require header files spanning the module adapter framework, audio buffer streaming APIs, and logging subsystems: + +* ````: Declares the foundational module adapter structures, including ``struct processing_module``, ``struct module_data``, and the ``struct module_interface`` callback table. +* ````: Defines the underlying component device representation (``struct comp_dev``), component status flags, and pipeline binding abstractions. +* ```` & ````: Provide circular buffer access primitives, allowing modules to inspect available frame counts, retrieve raw data pointers, and advance read/write pointers. +* ````: Provides high-level data copying utilities (e.g. ``source_to_sink_copy()``) for bypass and format conversions. +* ````: Exposes dictionary-based firmware logging macros (``comp_dbg``, ``comp_info``, ``comp_err``) that encode messages efficiently into compile-time trace entries for ``sof-logger``. +* ````: Supplies system initialization macros (``SOF_MODULE_INIT``) for static driver auto-registration during early firmware boot. .. code-block:: c @@ -153,7 +166,10 @@ Include the core module adapter and buffer management headers: Component Logging & Registration ================================ -Register the module's unique logging facility and define its runtime UUID: +To enable structured debugging without firmware recompilation, each module declares a unique logging domain and binds its runtime UUID symbol: + +* **UUID Symbol Definition (``SOF_DEFINE_REG_UUID``)**: Associates the component's internal C translation unit with the 128-bit UUID generated by ``scripts/gen-uuid-reg.py`` from ``uuid-registry.txt``. This symbol is used by the pipeline manager to match incoming host topology widgets to component driver instances. +* **Zephyr Log Facility Registration (``LOG_MODULE_REGISTER``)**: Registers the component with the Zephyr logging subsystem using a dedicated textual tag and default verbosity (``CONFIG_SOF_LOG_LEVEL``). This allows engineers to dynamically adjust trace output per component via host logging commands or IPC debug levels. .. code-block:: c @@ -166,7 +182,7 @@ Register the module's unique logging facility and define its runtime UUID: Lifecycle Callback State Machine ================================ -The module adapter exposes seven lifecycle hooks through ``struct module_interface``: +The module adapter exposes seven lifecycle hooks through ``struct module_interface`` that manage the component across its creation, preparation, active processing, and destruction: .. figure:: images/module_lifecycle_state_machine.svg :alt: Audio Module State Machine @@ -178,7 +194,13 @@ The module adapter exposes seven lifecycle hooks through ``struct module_interfa 1. Initialization (``init``) ---------------------------- -Invoked when the pipeline is instantiated. Allocates component private state using ``mod_zalloc()`` and sets default parameters: +The ``init()`` callback is invoked synchronously when the host audio driver instantiates an audio pipeline (e.g. during an IPC4 ``GLB_CREATE_PIPELINE`` or ``MOD_INIT_INSTANCE`` message). + +**Key Responsibilities & Rules in ``init()``**: +* **Allocate Component State**: Allocate the private component state structure (``struct my_filter_comp_data``) using ``mod_zalloc()``. This memory is zero-initialized and accounted against the module's heap quota. +* **Initialize State Flags**: Establish default parameter values, bypass states, and coefficient tables. +* **Do NOT Allocate Dynamic Audio Buffers**: Stream parameters (channel counts, sample rate, frame formats) are not yet finalized during ``init()``. Allocating audio delay lines or circular buffers here is premature; delay buffer allocations must be deferred to ``prepare()``. +* **Cold Memory Placement**: Mark ``init()`` with the ``__cold`` attribute so the compiler locates this one-time setup code in external DRAM rather than scarce DSP L1/L2 SRAM. .. code-block:: c @@ -206,7 +228,13 @@ Invoked when the pipeline is instantiated. Allocates component private state usi 2. Stream Preparation (``prepare``) ----------------------------------- -Invoked immediately prior to pipeline startup when stream audio formats (rate, channels, PCM sample format) are fully resolved. Binds the fast-path processing function pointer to avoid branching inside the real-time processing loop: +The ``prepare()`` callback is invoked immediately prior to starting active audio playback or capture, transitioning the component from ``COMP_STATE_READY`` to ``COMP_STATE_PREPARE``. At this juncture, the upstream and downstream buffer connections are fully resolved, and valid PCM audio parameters are available. + +**Key Responsibilities & Rules in ``prepare()``**: +* **Query Audio Stream Attributes**: Inspect connected sources and sinks to retrieve negotiated sample rates (``source_get_rate()``), channel counts (``source_get_channels()``), frame byte widths (``source_get_frame_bytes()``), and PCM formats (``source_get_frm_fmt()``). +* **Verify Pin Configuration**: Enforce the module's expected pin contract. If the component only supports a single input and single output pin, verify ``num_of_sources == 1`` and ``num_of_sinks == 1``; return ``-EINVAL`` if the topology is improperly routed. +* **Allocate Format-Dependent Buffers**: Allocate audio delay lines, circular scratch buffers, and history arrays using ``mod_balloc_align()`` based on the exact negotiated channel count and frame dimensions. +* **Bind Fast-Path Function Pointers**: Match the negotiated PCM sample format (e.g. ``SOF_IPC_FRAME_S16_LE``, ``SOF_IPC_FRAME_S32_LE``) to the optimal processing function (scalar reference or SIMD vector kernel) and cache the function pointer in ``cd->process_func``. This eliminates conditional format branching from the inner real-time processing loop. .. code-block:: c @@ -241,7 +269,14 @@ Invoked immediately prior to pipeline startup when stream audio formats (rate, c 3. Real-Time Processing (``process``) ------------------------------------- -Called periodically by the SOF scheduler during audio streaming. Must adhere to strict real-time audio constraints: +The ``process()`` callback is invoked periodically by the SOF scheduler thread whenever the component's pipeline scheduling tick fires (e.g. every 1 ms in low-latency mode, or every 10 ms / 20 ms in data-processing mode). + +**Key Responsibilities & Rules in ``process()``**: +* **Calculate Available Frame Budget**: Query the input source for available data frames (``source_get_data_frames_available()``) and the output sink for remaining free buffer space (``sink_get_free_frames()``). The allowable processing block size is strictly the minimum of both: ``frames = MIN(available, free)``. +* **Handle Zero-Frame Quotas Gracefully**: If either buffer has zero frames ready, exit immediately and return 0. Never block or wait for data inside ``process()``. +* **Execute Processing Kernel or Bypass**: If the component is enabled, call the cached ``cd->process_func()`` pointer to process audio samples from source to sink. If the module is disabled or in bypass mode, execute a pass-through copy using ``source_to_sink_copy()`` to guarantee continuous audio flow without latency discontinuities. +* **Advance Read & Write Pointers**: The audio processing kernel must update circular buffer read and write pointers (via ``audio_stream_produce()`` and ``audio_stream_consume()``) to reflect exactly how many frames were transformed. +* **Strict Real-Time Invariants**: Under no circumstances should ``process()`` allocate memory (``mod_alloc``, ``malloc``), acquire blocking mutexes with timeouts, or invoke non-deterministic kernel operations. Violating this rule will cause immediate audio glitching, buffer underruns, or hardware watchdog resets. .. code-block:: c @@ -274,7 +309,13 @@ Called periodically by the SOF scheduler during audio streaming. Must adhere to 4. Parameter Control (``set_configuration`` & ``get_configuration``) -------------------------------------------------------------------- -Handles runtime parameter injection (ALSA mixer switches, volume levels, EQ filter coefficient blobs): +The ``set_configuration()`` and ``get_configuration()`` callbacks handle runtime parameter injection and state queries from the host operating system. These callbacks are triggered when ALSA mixer controls, volume switches, or ``sof-ctl`` byte configuration blobs are transmitted across the IPC4 channel. + +**Key Responsibilities & Rules in Parameter Handling**: +* **Inspect Parameter IDs**: Match the incoming ``param_id`` against defined module control enumerations (e.g. ``MY_FILTER_PARAM_SWITCH`` for boolean mute/bypass, ``MY_FILTER_PARAM_COEFFICIENTS`` for filter biquad parameters). +* **Validate Payload Integrity**: Strictly verify that ``fragment_size`` matches or exceeds the expected data structure size before dereferencing pointers. Return ``-EINVAL`` on size mismatches to prevent memory corruption or malicious buffer overflows. +* **Handle Large Configuration Fragments**: If parameter blobs exceed standard IPC mailbox payload limits (typically 4 KB), the module adapter fragments the transfer. Inspect ``pos`` (``MODULE_CFG_FRAGMENT_SINGLE``, ``FIRST``, ``MIDDLE``, ``LAST``) to assemble multi-fragment payloads into scratch buffers before applying updates. +* **Atomic Parameter Updates**: When updating filter coefficients or acoustic model weights while audio is actively streaming, apply updates atomically or use double-buffered parameter structs to prevent audible clicks, pops, or mathematical instability in active filter state variables. .. code-block:: c @@ -305,7 +346,11 @@ Handles runtime parameter injection (ALSA mixer switches, volume levels, EQ filt 5. Reset & Teardown (``reset`` & ``free``) ------------------------------------------ -Clears runtime history when audio stops, and frees heap resources when the pipeline is destroyed: +The lifecycle concludes with ``reset()`` and ``free()``, which govern state cleanup and resource deallocation: + +* **``reset()`` (Pipeline Stop & State Reset)**: Invoked when audio streaming pauses or stops, transitioning the component to ``COMP_STATE_READY``. Memory allocations remain intact, but the component must reset its internal history—clearing delay lines, zeroing filter state variables, and resetting phase accumulators—so subsequent playback starts cleanly without stale audio echoes. +* **``free()`` (Pipeline Destruction & Resource Release)**: Invoked when the pipeline is deleted (e.g. during an IPC4 ``DELETE_INSTANCE`` message). The component must release all auxiliary memory allocated during ``prepare()`` (such as aligned delay buffers) and free its primary private data struct using ``mod_free()``. +* **Cold Teardown Verification**: Annotate ``free()`` with ``__cold`` and include ``assert_can_be_cold();`` to guarantee that memory destruction runs safely in cold execution contexts without consuming precious real-time SRAM. .. code-block:: c @@ -338,7 +383,10 @@ Clears runtime history when audio stops, and frees heap resources when the pipel Declaring the Module Interface ============================== -Bind operations into the static dispatch table and register the module: +To register the component with the SOF firmware dispatch subsystem, bind the lifecycle operations into a constant ``struct module_interface`` table and export the registration symbols: + +* **Static In-Tree Linking**: When compiled directly into the base firmware image (``CONFIG_COMP_MY_FILTER=y``), declare the trace context (``DECLARE_TR_CTX``), instantiate the adapter binding (``DECLARE_MODULE_ADAPTER``), and register with the system startup dispatcher (``SOF_MODULE_INIT``). +* **Dynamic Loadable Modules (LLEXT)**: When compiled as a dynamically loadable linkable extension (``CONFIG_COMP_MY_FILTER_MODULE=y``), export the module manifest using ``SOF_LLEXT_MODULE_MANIFEST()`` placed inside the dedicated ``.module`` ELF section alongside ``SOF_LLEXT_BUILDINFO``. .. code-block:: c @@ -375,32 +423,39 @@ Bind operations into the static dispatch table and register the module: Step 3: UUID Generation & Endianness Rules ****************************************** -Every SOF component is uniquely identified across the entire firmware subsystem, host topology parser, and Linux kernel driver by a 128-bit **Universally Unique Identifier (UUID)**. +Every SOF audio component is uniquely identified across the entire firmware subsystem, host ALSA topology parser, and Linux kernel driver by a 128-bit **Universally Unique Identifier (UUID)** formatted according to RFC 4122. + +Using 128-bit UUIDs rather than sequential integer identifiers allows third-party vendors, proprietary acoustic algorithms, and open-source modules to be authored and integrated independently without central namespace collisions or coordination bottlenecks. Generating the RFC 4122 UUID ============================ -Generate a version 4 UUID using standard Linux tools: +Audio component developers generate a random Version 4 UUID using standard Linux utilities: .. code-block:: bash uuidgen # Example output: a62de1af-5964-4e2e-b167-7fdc97279a29 +This 128-bit number serves as the permanent digital fingerprint of your algorithm across its entire lifecycle: +* It is embedded in the firmware manifest (static `.ri` binary or dynamic `.llext` package) generated by ``rimage``. +* It is referenced in the ALSA Topology 2.0 widget definition. +* When the Linux SOF driver loads the topology file, it matches the widget's UUID against the module dictionary exported by the DSP firmware, instantiating the correct module pipeline on demand. + Registering in ``uuid-registry.txt`` ==================================== -Append the UUID and component name to the global registry file located at the root of the SOF repository (`uuid-registry.txt `_): +Append the newly generated UUID and lowercase component name to the global registry file located at the root of the SOF repository (`uuid-registry.txt `_): .. code-block:: text # In $SOF_WORKSPACE/sof/uuid-registry.txt a62de1af-5964-4e2e-b167-7fdc97279a29 my_filter -The build system executes ``scripts/gen-uuid-reg.py`` to automatically generate: -- ``UUIDREG_STR_MY_FILTER`` (string literal for manifests and topology) -- ``SOF_DEFINE_REG_UUID(my_filter)`` (C variable definition in firmware) -- ``SOF_REG_UUID(my_filter)`` (macro reference) +During the CMake configuration phase, the build system invokes the Python preprocessor ``scripts/gen-uuid-reg.py`` against ``uuid-registry.txt`` to validate and generate: +* **Compile-Time Definitions**: Produces ``SOF_DEFINE_REG_UUID(my_filter)`` and ``SOF_REG_UUID(my_filter)`` in generated header ``sof/uuid-registry.h``. +* **String Literals**: Produces ``UUIDREG_STR_MY_FILTER`` for use in static firmware manifests. +* **Collision Detection**: Strictly verifies that no duplicate UUIDs or conflicting component names exist in the repository, failing the build immediately if a duplicate is introduced. .. warning:: **Crucial Architectural Pitfall: Little-Endian Word Swapping in ALSA Topology 2.0 & IPC4** @@ -464,12 +519,12 @@ Digital signal processors feature complex, non-uniform memory architectures (NUM Cold Code Directives (``__cold``) ================================= -Routines that execute only during setup or teardown must **never** occupy precious internal DSP L1/L2 SRAM. SOF uses the ``__cold`` attribute to instruct the compiler and linker to locate functions into cold memory sections loaded into slower, high-capacity DRAM. +Routines that execute only during setup or teardown must **never** occupy precious internal DSP L1/L2 SRAM. SOF uses the ``__cold`` attribute to instruct the compiler and linker script (``linker.ld``) to locate functions into the ``.text.cold`` and ``.data.cold`` memory sections. These sections are placed into external DRAM or slow low-power SRAM pools that can be paged out or put into low-power retention states during active audio processing. Rules for Cold-Code Placement: -- Mark ``init()`` and ``free()`` functions with ``__cold``. -- Inside ``free()``, insert the ``assert_can_be_cold();`` verification macro. -- Mark static filter coefficient tables or lookup tables (LUTs) used only during initialization with ``__cold_const``. +* **Initialization & Teardown**: Mark ``init()`` and ``free()`` functions with ``__cold``. +* **Runtime Verification**: Inside ``free()``, insert the ``assert_can_be_cold();`` verification macro. This asserts at runtime that interrupts are safely handled and that the DSP core is operating in an execution mode that tolerates DRAM access latencies. +* **Static Lookup Tables**: Mark static filter coefficient tables, FFT twiddle factor tables, or neural network model weight tables used only during setup with ``__cold_const``. .. code-block:: c @@ -491,24 +546,24 @@ Rules for Cold-Code Placement: Memory Allocation APIs ====================== -The module adapter framework provides managed memory allocators tracked per module instance: +The module adapter framework provides managed memory allocators tracked per module instance. Unlike generic system-wide ``malloc()``, module adapter allocators partition allocations by instance, track high-water memory marks, and guarantee that when a module instance is deleted, all associated heap memory is reclaimed without system leaks: .. list-table:: Module Memory Allocation Functions :widths: 35 65 :header-rows: 1 * - Allocator Function - - Intended Use Case + - Intended Use Case & Alignment Semantics * - ``mod_zalloc(mod, size)`` - - Allocates zero-initialized memory for general component state structures. Automatically aligned to ``PLATFORM_DCACHE_ALIGN``. + - Allocates zero-initialized memory for general component state structures. Automatically aligned to ``PLATFORM_DCACHE_ALIGN`` to prevent cache-coherency writeback hazards. * - ``mod_alloc_align(mod, size, align)`` - - Allocates memory with specific byte alignment (e.g. 16-byte for SIMD vectors). + - Allocates memory with explicit byte alignment (e.g. 16-byte alignment for 128-bit HiFi 4 SIMD registers). * - ``mod_balloc(mod, size)`` - - Allocates large buffer memory (e.g. multi-channel audio delay lines) from dedicated buffer heap pools. + - Allocates large buffer memory (e.g. multi-channel audio delay lines) from dedicated bulk buffer heap pools, preventing fragmentation of the general system heap. * - ``mod_balloc_align(mod, size, align)`` - - Allocates large audio buffers with strict hardware alignment. + - Allocates large audio delay lines and circular buffers with strict hardware alignment. * - ``mod_free(mod, ptr)`` - - Releases memory back to the module heap and updates high-water mark accounting. + - Releases memory back to the module heap and updates internal quota accounting. Safe to invoke in teardown. --- @@ -520,11 +575,15 @@ Step 5: Vector Data Alignment & SIMD Optimization (Optional - Optimization) Step 5 is an **optional optimization**. SOF audio modules typically start with a portable scalar C reference implementation in ``-generic.c`` that runs correctly across all architectures. Once the baseline audio algorithm is functionally verified, you can optionally implement architecture-specific SIMD vector acceleration (e.g. Tensilica HiFi 3/4/5, ARM Neon/Helium, RISC-V Vector) and enforce strict hardware memory alignment to maximize throughput and minimize MCPS. -To achieve real-time throughput within strict battery power budgets, audio DSP algorithms rely heavily on Single Instruction, Multiple Data (SIMD) vector processing. +To achieve real-time audio throughput within strict battery power envelopes, DSP algorithms rely heavily on Single Instruction, Multiple Data (SIMD) vector processing. Vector instruction pipelines execute arithmetic operations across multiple audio channels or consecutive PCM samples in a single clock cycle. Alignment Requirements by Architecture ====================================== +Modern audio DSP cores feature specialized vector load and store units engineered to move 64-bit, 128-bit, 256-bit, or 512-bit register payloads to and from data memory in a single clock cycle. However, these hardware units strictly require that memory operands be aligned to the natural boundary of the SIMD register width. + +On Cadence Tensilica HiFi DSPs, dereferencing a vector pointer that violates hardware alignment boundaries triggers a fatal processor exception (``EXCCAUSE = 9: LoadStoreAlignmentCause``). Because embedded DSP firmware operates without speculative MMU fixups or virtual memory trap handlers, an alignment fault results in an unrecoverable kernel panic, immediate watchdog reset, and audio failure. On architectures that tolerate unaligned memory accesses (such as certain ARM Cortex-M or x86 cores), unaligned transfers trigger multi-cycle bus serialization, pipeline stalls, and cross-cache-line split transactions that rapidly degrade real-time performance. + .. list-table:: Architecture Alignment Requirements :widths: 20 20 30 30 :header-rows: 1 @@ -557,7 +616,10 @@ Alignment Requirements by Architecture Declaring Aligned Structs & Buffers =================================== -When declaring delay lines, filter states, or scratch vectors: +When declaring state structures that encapsulate delay lines, coefficient tables, or scratch vectors, use compiler alignment attributes to guarantee both inner member alignment and outer cache-line alignment: + +* **Inner Member Alignment (``__aligned(16)``)**: Applying ``__aligned(16)`` to multidimensional delay line arrays guarantees that the compiler places the array on a 16-byte boundary and pads intermediate structures. This allows inner SIMD loops to load consecutive audio samples directly into 128-bit HiFi 4 vector registers without pointer adjustment. +* **Outer Cache-Line Alignment (``__aligned(PLATFORM_DCACHE_ALIGN)``)**: Annotating the enclosing component struct with ``__aligned(PLATFORM_DCACHE_ALIGN)`` (typically 64 or 128 bytes depending on the Intel CAVS or ACE platform) aligns the entire structure to a CPU/DSP data cache line. In multi-core configurations where distinct DSP cores process parallel audio streams, this prevents *false sharing*—a performance hazard where two cores modify adjacent data sharing the same cache line, triggering frequent cache invalidation bus snoops and pipeline flushes. .. code-block:: c @@ -575,11 +637,17 @@ When declaring delay lines, filter states, or scratch vectors: Allocating Aligned Buffers at Runtime ===================================== -When allocating audio delay buffers dynamically during ``prepare()``: +Because audio stream geometry (such as channel counts, sampling frequencies, and frame block sizes) is negotiated dynamically during stream creation, audio delay lines and circular history buffers must be sized and allocated dynamically during the ``prepare()`` callback. + +Standard C heap allocators (such as ``malloc()``) only guarantee word alignment (4 or 8 bytes), which fails to satisfy 16-byte HiFi 4 or 32-byte HiFi 5 vector load requirements. The module adapter framework provides ``mod_balloc_align()``, which allocates memory from dedicated bulk buffer heap pools and guarantees exact power-of-two byte alignment: + +* **Buffer Sizing Formula**: Calculate total buffer bytes as ``channels * delay_frames * sizeof(int32_t)``. Always ensure this computation cannot overflow 32-bit integer limits. +* **Alignment Validation**: Verify that the returned pointer satisfies the required alignment boundary using ``IS_ALIGNED()`` or explicit bitmasking (``((uintptr_t)ptr & (alignment - 1)) == 0``). +* **Buffer Lifetime**: Memory allocated via ``mod_balloc_align()`` remains valid throughout active streaming and must be explicitly released in ``free()`` using ``mod_free()`` to avoid memory pool leaks. .. code-block:: c - /* Allocate a 16-byte aligned circular delay line buffer */ + /* Allocate a 16-byte aligned circular delay line buffer during prepare() */ size_t buffer_bytes = cd->channels * MAX_DELAY_FRAMES * sizeof(int32_t); cd->delay_buffer = mod_balloc_align(mod, buffer_bytes, 16); if (!cd->delay_buffer) { @@ -587,24 +655,44 @@ When allocating audio delay buffers dynamically during ``prepare()``: return -ENOMEM; } + /* Verify hardware alignment invariant before streaming */ + assert(((uintptr_t)cd->delay_buffer & 0xF) == 0); + Scalar vs. Vectorized Separation ================================ -Maintain clean code separation: -1. **``my_filter-generic.c``**: Pure ISO C99 scalar implementation. Must compile and execute identically on host POSIX, x86-64, ARM, and Xtensa. -2. **``my_filter-hifi4.c``**: Hardware-accelerated SIMD implementation utilizing Cadence Xtensa HiFi 4 C intrinsics (``ae_int32x4``, ``AE_MULFP32X2RAS``, ``AE_L32X2_XC``). Guard this file under ``#if CONFIG_COMP_MY_FILTER_HIFI4``. +To maintain portability and ease firmware maintenance, SOF strictly decouples the high-level module lifecycle adapter from the underlying mathematical signal processing routines. The signal processing implementation is organized into two distinct layers: + +1. **Portable Scalar Reference (``my_filter-generic.c``)**: + A clean, highly readable ISO C99 scalar implementation. This file contains no proprietary DSP intrinsics, assembly directives, or platform-specific headers. + * **Workstation Portability**: Compiles cleanly under standard host toolchains (GCC, Clang) for fast offline debugging in the Host Testbench (Step 8) without requiring DSP cross-compilers. + * **Universal Fallback**: Serves as the fallback processing path on low-power background DSP cores or alternative processor architectures (such as ARM or RISC-V) that lack Tensilica HiFi vector units. + * **Mathematical Golden Reference**: Provides an unoptimized, bit-exact standard against which vectorized SIMD implementations can be mathematically verified for numerical accuracy and rounding behavior. + +2. **Hardware-Accelerated Vector Kernel (``my_filter-hifi4.c``)**: + A specialized SIMD implementation leveraging Cadence Tensilica HiFi 4 C intrinsics (e.g. ``ae_int32x4``, ``AE_MULFP32X2RAS``, ``AE_L32X2_XC``), circular addressing pointer registers, and zero-overhead hardware loops. + * **Compilation Gating**: Enclosed under ``#if CONFIG_COMP_MY_FILTER_HIFI4`` so that it is compiled only when targeting DSP architectures equipped with the required hardware execution units. + * **Fast-Path Dynamic Binding**: During ``prepare()``, the module adapter queries the stream PCM format and CPU capabilities, assigning the function pointer ``cd->process_func`` to either the vectorized kernel or the scalar fallback. During real-time streaming, ``process()`` invokes ``cd->process_func()`` directly, eliminating runtime conditionals and branch prediction penalties from inner sample loops. --- Step 6: CMake & Kconfig Build Integration ***************************************** -SOF firmware builds with **Zephyr CMake** and the **Kconfig** configuration system. +SOF firmware integrates with the **Zephyr CMake** build system and the **Kconfig** configuration framework. This ensures that audio components are modular, configurable per platform, and capable of building either statically in-tree or dynamically as loadable extensions. Defining Component Kconfig ========================== -Create ``src/audio/my_filter/Kconfig``: +Kconfig files declare the user-configurable options, dependencies, and compilation flags for your audio module. Create ``src/audio/my_filter/Kconfig``: + +* **Tristate Option (``tristate "Custom Audio Filter Component"``)**: + Declaring ``COMP_MY_FILTER`` as a tristate option allows the module to be configured in three states: + - ``y``: Statically compiled and linked directly into the primary base firmware binary (e.g. ``sof-ptl.ri``). + - ``m``: Compiled as an isolated, dynamically loadable linkable extension (``.llext``) package that can be stored on the host filesystem and loaded on demand by the kernel driver. + - ``n``: Completely omitted from the build, leaving zero memory footprint in the resulting image. +* **Hardware Architecture Dependencies (``depends on``)**: + The SIMD acceleration option ``COMP_MY_FILTER_HIFI4`` specifies ``depends on COMP_MY_FILTER && XTENSA_HAVE_HIFI4``. This guarantees that vectorized code is only compiled when the parent component is selected and the target DSP architecture physically includes Cadence HiFi 4 execution units. .. code-block:: kconfig @@ -630,6 +718,13 @@ Create ``src/audio/my_filter/Kconfig``: Defining Component ``CMakeLists.txt`` ===================================== +The component ``CMakeLists.txt`` directs the compiler which source files to assemble based on active Kconfig configuration symbols: + +* **LLEXT Dynamic Target Delegation**: When built as a dynamic module (``CONFIG_COMP_MY_FILTER STREQUAL "m"``), CMake delegates the build to the ``llext/`` subdirectory and registers an explicit build dependency on the main application (``add_dependencies(app my_filter)``). +* **Static In-Tree Compilation**: When built directly into the base firmware, ``add_local_sources(sof ...)`` appends the lifecycle adapter (``my_filter.c``) and scalar reference kernel (``my_filter-generic.c``) to the main ``sof`` static library target. +* **SIMD Kernel Inclusion**: Conditionally appends the vectorized implementation (``my_filter-hifi4.c``) only if ``CONFIG_COMP_MY_FILTER_HIFI4`` is enabled. +* **IPC Protocol Version Dispatch**: SOF supports multiple IPC protocols across generations. Using ``CONFIG_IPC_MAJOR_4`` and ``CONFIG_IPC_MAJOR_3``, CMake conditionally compiles the appropriate parameter serializer (``my_filter-ipc4.c`` for modern Intel CAVS and ACE platforms or ``my_filter-ipc3.c`` for legacy architectures). + Create ``src/audio/my_filter/CMakeLists.txt``: .. code-block:: cmake @@ -659,13 +754,42 @@ Create ``src/audio/my_filter/CMakeLists.txt``: Registering in Parent Build Files ================================= -1. Add ``rsource "my_filter/Kconfig"`` to `src/audio/Kconfig `_. -2. Add ``add_subdirectory_ifdef(CONFIG_COMP_MY_FILTER my_filter)`` to `src/audio/CMakeLists.txt `_. +To integrate the new component into the global SOF firmware build graph, register the component directory in the top-level audio subsystem build files: + +1. **Register Kconfig Discovery (`src/audio/Kconfig `_)**: + Add the ``rsource`` (relative source) statement: + + .. code-block:: kconfig + + rsource "my_filter/Kconfig" + + This ingests your component's configuration options into the Zephyr Kconfig menu, making them selectable in platform configuration files (such as ``prj.conf``) or via the interactive configuration interface (``west build -t menuconfig``). + +2. **Register CMake Subdirectory (`src/audio/CMakeLists.txt `_)**: + Add the conditional subdirectory directive: + + .. code-block:: cmake + + add_subdirectory_ifdef(CONFIG_COMP_MY_FILTER my_filter) + + The ``add_subdirectory_ifdef`` macro inspects the Kconfig symbol at configuration time. If ``CONFIG_COMP_MY_FILTER`` is disabled (``n``), CMake skips traversing the subdirectory entirely, preserving fast configuration times and preventing namespace clutter. Multi-Toolchain Compatibility Verification ========================================== -Verify that your code compiles across all three supported toolchains: +SOF is an open-source firmware ecosystem deployed across diverse silicon platforms and verified in continuous integration (CI) pipelines worldwide. To prevent platform breakages and guarantee code portability, all audio components must build cleanly without warnings or errors across three supported toolchains: + +1. **Cadence Xtensa Tools (``xt-clang`` / ``xt-xcc``)**: + The proprietary vendor compiler provided by Cadence. Generates the most highly optimized machine code for Xtensa HiFi DSP architectures by applying hardware-specific scheduling, register allocation, and intrinsic expansions. +2. **Zephyr SDK (``xtensa-zephyr-elf-gcc``)**: + The open-source GCC cross-compiler distributed with the Zephyr Project. Widely used by open-source developers and automated community pull request verification pipelines. +3. **Shared LLVM/Clang with Integrated Assembler (IAS)**: + Modern LLVM toolchain. SOF enforces the **Integrated Assembler (IAS) mandatory policy**, requiring that all assembly directives and inline assembly constructs conform strictly to standard LLVM Xtensa assembler definitions rather than GNU gas legacy workarounds. + +**Common Compiler Divergence Traps**: +* **Variable-Length Arrays (VLAs)**: Declaring runtime arrays (e.g. ``int32_t buf[frames];``) is strictly forbidden. VLAs cause stack frame blowups and are rejected by embedded coding guidelines. +* **Non-Standard Compiler Extensions**: Statements such as nested functions, non-standard statement expressions, or GNU-specific attribute placements that compile under GCC will fail under ``xt-clang``. +* **Strict Diagnostic Flags (``-Wall -Wextra -Werror``)**: Any unused function argument, implicit sign conversion, or uninitialized variable will cause an immediate build abort in SOF CI pipelines. .. list-table:: SOF Toolchain Build Validation Commands :widths: 25 35 40 @@ -689,12 +813,28 @@ Verify that your code compiles across all three supported toolchains: Step 7: ALSA Topology 2.0 Integration ************************************* -To instantiate the new component inside an audio pipeline graph, define its ALSA Topology 2.0 configuration. +To instantiate the audio component inside an audio processing graph, define its configuration in **ALSA Topology 2.0**. + +ALSA Topology 2.0 uses structured, object-oriented configuration files (``.conf``) to represent complete audio pipeline graphs—including buffer dimensions, scheduling periods, hardware DAIs, mixer controls, and DSP processing modules. These text definitions are compiled by ``alsatplg`` into binary topology files (``.tplg``) that the Linux kernel driver reads during boot to dynamically create and route DSP pipelines via IPC4 or IPC3 commands. Defining Component Topology Widget ================================== -Create `tools/topology/topology2/include/components/my_filter.conf `_: +Create the widget class definition in `tools/topology/topology2/include/components/my_filter.conf `_: + +* **Class Definition (``Class.Widget."my_filter"``)**: + Declares a reusable widget class that inherits base attributes and memory capabilities from ````. +* **Constructor & Instance Attributes**: + - ``index``: The pipeline identifier to which this component instance belongs. + - ``instance``: A unique numeric identifier distinguishing multiple instances of the same filter within the topology. +* **Mandatory Validation Attributes (``!mandatory``)**: + Enforces that any pipeline instantiating ``my_filter`` must explicitly specify input and output pin quotas (``num_input_pins``, ``num_output_pins``) and valid PCM audio format lists. +* **UUID Token Binding**: + The ``uuid`` field contains the **word-swapped little-endian hex GUID string** derived in Step 3. When the Linux SOF driver parses this widget from the ``.tplg`` file, it matches this GUID against the module manifest exported by the DSP firmware, instantiating the correct module dispatch entry. +* **Runtime ALSA Controls (``Object.Control``)**: + Declares interactive mixer switches or byte controls exposed to host userspace: + - ``mixer."1"``: Generates an ALSA volume/switch control (e.g. "My Filter Switch") using standard ``volsw`` semantics. Changing this switch via ``amixer`` or ``alsamixer`` triggers an IPC configuration message received by ``set_configuration()``. + - ``bytes."1"``: Can be declared to expose raw binary configuration blobs (such as parametric equalizer biquad coefficients or acoustic tuning profiles) updated via ``sof-ctl``. .. code-block:: text @@ -761,7 +901,10 @@ Create `tools/topology/topology2/include/components/my_filter.conf ``: Path to the compiled ALSA topology binary defining the pipeline graph. + * ``-i in.raw``: Input raw PCM audio file. + * ``-o out.raw``: Output raw PCM audio file capturing the processed result. + + .. code-block:: bash + + tools/testbench/build_testbench/install/bin/sof-testbench4 \ + -r 48000 -c 2 -b S32_LE -p 1,2 \ + -t tools/build_tools/topology/topology2/development/sof-hda-benchmark-myfilter32.tplg \ + -i in.raw -o out.raw + +3. **Inspect Output Audio & Verify Signal Quality**: + Convert the raw output file back to a standard WAV container. Inspect the resulting waveform in Audacity or listen via ``aplay`` to confirm that filtering, gain adjustment, or noise reduction behaves as intended without audible distortion or clipping: + + .. code-block:: bash + + sox -L -r 48000 -c 2 -b 32 out.raw out.wav + aplay out.wav Simulating Dynamic Control Injections ===================================== -Create a control script ``controls.sh`` to simulate runtime ``amixer`` or ``sof-ctl`` commands: +Real-time audio processing rarely operates with static parameters. In production environments, host software continuously modifies mixer controls, toggles bypass switches, and injects filter coefficient presets over IPC. + +The Host Testbench supports automated runtime parameter injection using the ``-s