diff --git a/src/audio/module_adapter/iadk/README.md b/src/audio/module_adapter/iadk/README.md new file mode 100644 index 000000000000..d04ea1924bd8 --- /dev/null +++ b/src/audio/module_adapter/iadk/README.md @@ -0,0 +1,85 @@ +# Intel Audio Development Kit (`module_adapter/iadk`) + +The `iadk` directory provides the Module Adapter implementation for external 3rd-party audio algorithms developed using the **Intel Audio Development Kit (IADK)**. + +Unlike the native SOF `module_interface` API (written primarily in C), the IADK modules are object-oriented C++ classes that derive from `intel_adsp::ProcessingModuleInterface`. The SOF `IadkModuleAdapter` acts as a C++ to C "glue layer" that wraps these IADK methods so they can be natively plugged into the SOF `module_adapter` pipeline without modification to the module's pre-compiled binary. + +## Architecture and Class Hierarchy + +The system defines an `IadkModuleAdapter` class which internally holds an instance of the 3rd-party `ProcessingModuleInterface`. + +```mermaid +classDiagram + class SOF_ModuleAdapter { + +init + +process + +bind + } + + class iadk_wrapper { + iadk_wrapper_process + iadk_wrapper_init + } + + class IadkModuleAdapter { + -processing_module_ + +IadkModuleAdapter_Init + +IadkModuleAdapter_Process + +IadkModuleAdapter_SetConfiguration + } + + class ProcessingModuleInterface { + +Init + +Process + +SetConfiguration + +Reset + } + + class IADK_3rdParty_Algorithm { + +Init + +Process + } + + SOF_ModuleAdapter --> iadk_wrapper : C Function Pointers + iadk_wrapper --> IadkModuleAdapter : Instantiates and Wraps + IadkModuleAdapter --> ProcessingModuleInterface : Polymorphic Interface + ProcessingModuleInterface <|-- IADK_3rdParty_Algorithm : Inherits +``` + +## System Agent and Instantiation Flow + +Because the actual module resides in an external binary, it requires a "System Agent" to correctly instantiate the C++ objects during the component's `init` phase. + +1. The OS host driver sends an IPC `INIT_INSTANCE` command for the module. +2. The `system_agent_start()` function intercepts this, invokes the dynamic module's `create_instance` entry point (which invokes a `ModuleFactory`). +3. The `SystemAgent` deduces the pin count (interfaces) and initial pipeline configurations using `ModuleInitialSettingsConcrete`. +4. The factory allocates the concrete algorithm and checks it back into SOF through `SystemAgent::CheckIn`. + +```mermaid +sequenceDiagram + participant IPC + participant SA + participant Fac + participant Mod + + IPC->>SA: Trigger Mod Creation + SA->>Fac: CI invokes create_instance + Fac->>Fac: Deduce BaseModuleCfgExt + Fac->>Mod: operator new instantiate + + Fac->>SA: SystemAgent CheckIn Module + SA->>Adp: Create new IadkModuleAdapter Module + + SA-->>IPC: Return CPP adapter to C Pipeline +``` + +## Data Buffer Translation + +A significant task of `IadkModuleAdapter_Process` is converting SOF's underlying buffer formats to IADK's `InputStreamBuffer` and `OutputStreamBuffer` structures. + +Instead of letting the module directly touch the SOF `comp_buffer` (which could change with SOF version updates), the adapter uses the abstraction APIs (`source_get_data` / `sink_get_buffer`) and wraps them: + +1. Request raw continuous memory pointers from `source_get_data()`. +2. Construct an `intel_adsp::InputStreamBuffer` pointing to that continuous memory chunk. +3. Call the IADK `processing_module_.Process()`. +4. Release precisely the amount of consumed data using `source_release_data()`. diff --git a/src/audio/module_adapter/library/README.md b/src/audio/module_adapter/library/README.md new file mode 100644 index 000000000000..3dd9aee14f7c --- /dev/null +++ b/src/audio/module_adapter/library/README.md @@ -0,0 +1,80 @@ +# Loadable Library & Userspace Proxy (`module_adapter/library`) + +The `library` directory within the module adapter manages the lifecycle and execution isolation for dynamically loaded algorithms, often referred to as "LLEXT modules" or "Userspace Modules". + +It acts as a secure intermediary layer between the native SOF/Zephyr kernel execution mode (supervisor mode) and the 3rd-party module running in an isolated user-mode context. By relying on Zephyr's Userspace mechanisms (`k_mem_domain`, `K_USER` threads), a faulting or misbehaving loadable extension cannot crash the entire firmware. + +## Architecture & Userspace Sandbox + +The Userspace Proxy architecture involves: + +- **`struct userspace_context`**: This encapsulates the memory domain (`k_mem_domain`) mapped exclusively for this module (code, rodata, BSS, and private heap memory). +- **`user_work_item`**: A Zephyr `k_work_user` mechanism. IPC configuration commands and data processing calls are packaged into a work item and executed safely inside the memory boundaries via `userspace_proxy_worker_handler`. + +```mermaid +graph TD + subgraph SOF Supervisor Domain + P["Pipeline DP Scheduler"] + MADP["Module Adapter Context"] + PROXY["Userspace Proxy (userspace_proxy_invoke)"] + end + + subgraph Zephyr Userspace Domain + SYSAGENT["LLEXT System Agent"] + HANDLER["Worker Handler (userspace_proxy_handle_request)"] + MOD["External Loadable Module (struct module_interface)"] + end + + P -->|Triggers process| MADP + MADP -->|Invokes Proxy| PROXY + + PROXY -->|Packages params & k_work| HANDLER + + HANDLER -->|Safe Call Context| MOD + SYSAGENT -.->|Bootstraps| MOD + + style SOF Supervisor Domain fill:#1d2951,stroke:#333 + style Zephyr Userspace Domain fill:#2e0b1a,stroke:#333 +``` + +## State Transitions & IPC Handling + +IPC messages arriving from the host (e.g. `SET_CONF`, `GET_CONF`, `MODULE_INIT`, `MODULE_BIND`) first hit the Module Adapter running in Supervisor Mode. The adapter checks its configuration and invokes the Userspace Proxy. The proxy performs the following context switch: + +```mermaid +sequenceDiagram + participant IPC as SOF IPC Task + participant MA as Module Adapter + participant Proxy as Userspace Proxy + participant Worker as Zephyr k_work_user Thread + participant UserMod as LLEXT Module + + IPC->>MA: IPC SET_CONF config_id + MA->>Proxy: userspace_proxy_set_configuration + + note over Proxy: 1. Package proxy params + note over Proxy: 2. Add Mailbox memory to `k_mem_domain` + note over Proxy: 3. Post `k_work_user` / event + + Proxy->>Worker: Context Switch -> User Mode + + Worker->>UserMod: module_interface set_configuration + + note over UserMod: Parse config payload safely + + UserMod-->>Worker: Return status + Worker-->>Proxy: Signal Task Done + + note over Proxy: Remove Mailbox memory from domain + + Proxy-->>MA: Return execution + MA-->>IPC: Send Reply/Status to Host +``` + +### Memory Domain Adjustments + +A crucial aspect of the userspace proxy is dynamic memory permission elevation: + +- Normally, the userspace module can only access its own `heap`, `data`, and `bss` partitions. +- When an IPC like `GET_CONF` requests large IPC buffers, the proxy temporarily adds the hardware `MAILBOX_HOSTBOX` into the module's `k_mem_domain` using `k_mem_domain_add_partition()`. +- Once the userspace thread returns, that hardware window is immediately removed from the memory domain to minimize the vulnerability window. diff --git a/src/audio/module_adapter/module/README.md b/src/audio/module_adapter/module/README.md new file mode 100644 index 000000000000..4586f060f37d --- /dev/null +++ b/src/audio/module_adapter/module/README.md @@ -0,0 +1,88 @@ +# Module Implementing Interfaces (`module_adapter/module`) + +The `module` directory contains the concrete `module_interface` implementations that bridge the core SOF Component API with various audio processing algorithms. These adapters translate the generic initialization, parameter configuration, and process loop calls into the specific operational sequences required by different module frameworks. + +The directory primarily houses three distinct module adapters: + +1. **Generic Core Adapter (`generic.c`)** +2. **Modules (IADK/Shim) Adapter (`modules.c`)** +3. **Cadence DSP Codec Adapter (`cadence.c`, `cadence_ipc4.c`)** + +## 1. Generic Core Adapter (`generic.c`) + +The Generic adapter provides the default runtime container for typical native SOF processing modules (e.g., volume, eq, src). + +It implements the full `struct module_interface` contract: + +* **Memory Management**: It intercepts memory allocation mappings using `mod_balloc_align` and tracks memory requests in a module-specific resource pool (`module_resource`). When the module goes out of scope, the framework garbage-collects any leaked allocations automatically via `mod_free_all()`. +* **Configuration Handling**: Manages large blob configuration messages across multiple IPC fragments (`module_set_configuration`). It allocates memory for `runtime_params` until the blob is fully assembled, then triggers the underlying algorithm with the completed struct. +* **State Machine Enforcement**: It wraps `process_audio_stream` and `process_raw_data` calls to verify the module is in either `MODULE_IDLE` or `MODULE_PROCESSING` states before execution. + +## 2. Modules (IADK Shim) Adapter (`modules.c`) + +The `modules.c` base is an extension adapter designed specifically to run Intel Audio Development Kit (IADK) 3rd party algorithms. + +Unlike the generic modules, the IADK modules are object-oriented C++ architectures linked into a separate library (`module_adapter/iadk`). This file acts as the primary C entry point wrapper. + +* It utilizes the `iadk_wrapper_*` C-bridge functions to invoke methods on the C++ `intel_adsp::ProcessingModuleInterface` classes. +* It exposes the standard `DECLARE_MODULE_ADAPTER(processing_module_adapter_interface)` that is bound to the SOF pipeline. + +## 3. Cadence Codec Adapter (`cadence.c`) + +This is a highly specialized adapter used for integrating Xtensa Audio (XA) codecs from Cadence (e.g., MP3, AAC, Vorbis, SBC, DAB). + +The `cadence.c` implementation maps the standard SOF pipeline controls into the Cadence memory buffer management and synchronous execution models. + +```mermaid +graph TD + subgraph SOF Initialization + INIT["cadence_codec_init"] + CONFIG["cadence_configure_codec_params"] + RESOLVE["cadence_codec_resolve_api"] + end + + subgraph Cadence Library Init XA API + MEMTABS["cadence_codec_init_memory_tables"] + GETSIZE["XA_API_CMD_GET_MEM_INFO_SIZE"] + SETPTR["XA_API_CMD_SET_MEM_PTR"] + end + + subgraph Data Processing Loop + PROC["cadence_codec_process_data"] + FILL["XA_API_CMD_SET_INPUT_BYTES"] + EXEC["XA_API_CMD_EXECUTE"] + GETOUT["XA_API_CMD_GET_OUTPUT_BYTES"] + end + + INIT --> RESOLVE + RESOLVE --> CONFIG + CONFIG --> MEMTABS + + MEMTABS -. Iterates Memory Types .-> GETSIZE + GETSIZE -. Assigns Buffers .-> SETPTR + + SETPTR ==>|Pipeline Trigger| PROC + + PROC --> FILL + FILL --> EXEC + EXEC --> GETOUT +``` + +### Cadence Memory Tables + +Unlike standard modules that directly read from a `sof_source` API, Cadence codecs require their memory isolated exclusively into exact predefined chunks categorized by "type". `cadence_codec_init_memory_tables` iterates through the codec's hardware definition to construct these memory areas: + +* `XA_MEMTYPE_INPUT` +* `XA_MEMTYPE_OUTPUT` +* `XA_MEMTYPE_SCRATCH` +* `XA_MEMTYPE_PERSIST` + +The SOF adapter allocates tracking structures via `mod_alloc_align` for each of these mandatory regions prior to audio playback. + +### Execution Wrapper + +During `cadence_codec_process()`, the adapter: + +1. Performs `source_get_data` and mechanically copies audio bytes into the isolated `XA_MEMTYPE_INPUT` buffer. +2. Invokes the Xtensa Audio codec API (`XA_API_CMD_EXECUTE`). +3. Reads the produced byte count and copies them back out from `XA_MEMTYPE_OUTPUT` into the `sof_sink`. diff --git a/src/module/README.md b/src/module/README.md new file mode 100644 index 000000000000..3d79c948bdd8 --- /dev/null +++ b/src/module/README.md @@ -0,0 +1,111 @@ +# Audio Processing Modules (`src/module`) + +The `src/module` directory and the `src/include/module` headers define the Sound Open Firmware (SOF) modern Audio Processing Module API. This architecture abstracts the underlying OS and pipeline scheduler implementations from the actual audio signal processing logic, allowing modules to be written once and deployed either as statically linked core components or as dynamically loadable Zephyr EXT (LLEXT) modules. + +## Architecture Overview + +The SOF module architecture is built around three core concepts: + +1. **`struct module_interface`**: A standardized set of operations (`init`, `prepare`, `process`, `reset`, `free`, `set_configuration`) that every audio processing module implements. +2. **`struct processing_module`**: The runtime instantiated state of a module. It holds metadata, configuration objects, memory allocations (`module_resources`), and references to interconnected streams. +3. **Module Adapter (`src/audio/module_adapter`)**: The system glue layer. It masquerades as a legacy pipeline `comp_dev` to the SOF schedulers, but acts as a sandbox container for the `processing_module`. It intercepts IPC commands, manages the module's state machine, manages inputs/outputs, and safely calls the `module_interface` operations. + +```mermaid +graph TD + subgraph SOF Core Pipeline Scheduler + P[Pipeline Scheduler
LL/DP Domains] + end + + subgraph Module Adapter System Layer + MA[Module Adapter `comp_dev`] + IPC[IPC Command Dispatch] + MEM[Memory Resource Manager] + end + + subgraph Standardized Module Framework API + INF[`module_interface` Ops] + SRC[Source API
`source_get_data`] + SNK[Sink API
`sink_get_buffer`] + end + + subgraph Custom Audio Modules + VOL[Volume] + EQ[EQ FIR/IIR] + CUSTOM[Loadable 3rd Party
Zephyr LLEXT] + end + + P <-->|Execute| MA + IPC -->|Config/Triggers| MA + + MA -->|Invoke| INF + MA -->|Manage| MEM + + INF --> VOL + INF --> EQ + INF --> CUSTOM + + VOL -->|Read| SRC + VOL -->|Write| SNK + EQ -->|Read| SRC + EQ -->|Write| SNK +``` + +## Module State Machine + +Every processing module is strictly governed by a uniform runtime state machine managed by the `module_adapter`. Modules must adhere to the transitions defined by `enum module_state`: + +1. `MODULE_DISABLED`: The module is loaded but uninitialized, or has been freed. No memory is allocated. +2. `MODULE_INITIALIZED`: After a successful `.init()` call. The module parses its IPC configuration and allocates necessary local resources (delay lines, coefficient tables). +3. `MODULE_IDLE`: After a successful `.prepare()` call. Audio stream formats are fully negotiated and agreed upon (Stream params, channels, rate). +4. `MODULE_PROCESSING`: When the pipeline triggers a `START` command. The `.process()` callback is actively handling buffers. + +```mermaid +stateDiagram-v2 + [*] --> MODULE_DISABLED: Module Created + + MODULE_DISABLED --> MODULE_INITIALIZED: .init() / IPC NEW + MODULE_INITIALIZED --> MODULE_DISABLED: .free() / IPC FREE + + MODULE_INITIALIZED --> MODULE_IDLE: .prepare() / Pipeline Setup + MODULE_IDLE --> MODULE_INITIALIZED: .reset() / Pipeline Reset + + MODULE_IDLE --> MODULE_PROCESSING: .trigger(START) / IPC START + MODULE_PROCESSING --> MODULE_IDLE: .trigger(STOP/PAUSE) / IPC STOP +``` + +## Data Flows and Buffer Management + +Modules do not directly manipulate underlying DMA, ALSA, or Zephyr `comp_buffer` pointers. Instead, they interact via the decoupled **Source and Sink APIs**. This allows the adapter to seamlessly feed data from varying topological sources without changing module code. + +The flow operates primarily in a "get -> manipulate -> commit/release" pattern: + +```mermaid +sequenceDiagram + participant Adapter as Module Adapter + participant Mod as Processing Module (.process) + participant Src as Source API (Input) + participant Snk as Sink API (Output) + + Adapter->>Mod: Process Trigger (sources[], sinks[]) + + Mod->>Src: source_get_data(req_size) + Src-->>Mod: Provides read_ptr, available_bytes + + Mod->>Snk: sink_get_buffer(req_size) + Snk-->>Mod: Provides write_ptr, free_bytes + + note over Mod: Execute DSP Algorithm
(Read from read_ptr -> Write to write_ptr) + + Mod->>Src: source_release_data(consumed_bytes) + Mod->>Snk: sink_commit_buffer(produced_bytes) + + Mod-->>Adapter: Return Status +``` + +### Source API (`src/module/audio/source_api.c`) +- modules request data by calling `source_get_data_s16` or `s32`. This establishes an active read lock. +- Once done, the module must call `source_release_data()` releasing only the frames actually consumed. + +### Sink API (`src/module/audio/sink_api.c`) +- modules request destination buffers by calling `sink_get_buffer_s16` or `s32`. +- After processing into the provided memory array, the module marks the memory as valid by calling `sink_commit_buffer()` for the exact number of frames successfully written.