Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions src/audio/module_adapter/iadk/README.md
Original file line number Diff line number Diff line change
@@ -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()`.
80 changes: 80 additions & 0 deletions src/audio/module_adapter/library/README.md
Original file line number Diff line number Diff line change
@@ -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.
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The statement 'cannot crash the entire firmware' is an absolute guarantee that may be stronger than what userspace isolation typically provides (it can reduce impact, but not necessarily prevent all failure modes like resource exhaustion). Consider rephrasing to a bounded/accurate claim (e.g., 'helps prevent a fault from taking down the whole firmware' / 'reduces the blast radius').

Suggested change
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.
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 is less likely to take down the entire firmware and its impact is better contained.

Copilot uses AI. Check for mistakes.

## 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.
88 changes: 88 additions & 0 deletions src/audio/module_adapter/module/README.md
Original file line number Diff line number Diff line change
@@ -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()`.
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This refers to a module_resource pool, but the new src/module/README.md describes memory allocations as module_resources (plural). Within this PR’s docs, please standardize the naming (and ideally match the actual struct/type name in the codebase) to avoid confusion.

Suggested change
* **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()`.
* **Memory Management**: It intercepts memory allocation mappings using `mod_balloc_align` and tracks memory requests in a module-specific resource pool (`module_resources`). When the module goes out of scope, the framework garbage-collects any leaked allocations automatically via `mod_free_all()`.

Copilot uses AI. Check for mistakes.
* **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`.
111 changes: 111 additions & 0 deletions src/module/README.md
Original file line number Diff line number Diff line change
@@ -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 <br> 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 <br> `source_get_data`]
SNK[Sink API <br> `sink_get_buffer`]
end

subgraph Custom Audio Modules
VOL[Volume]
EQ[EQ FIR/IIR]
CUSTOM[Loadable 3rd Party <br> 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 <br> (Read from read_ptr -> Write to write_ptr)

Mod->>Src: source_release_data(consumed_bytes)
Mod->>Snk: sink_commit_buffer(produced_bytes)
Comment on lines +91 to +100
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sequence diagram uses consumed_bytes / produced_bytes, but the subsequent bullets in the Source/Sink API sections describe releasing/committing 'frames'. Please make the unit consistent (bytes vs frames) across the diagram and the bullet points to avoid incorrect usage.

Suggested change
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 <br> (Read from read_ptr -> Write to write_ptr)
Mod->>Src: source_release_data(consumed_bytes)
Mod->>Snk: sink_commit_buffer(produced_bytes)
Mod->>Src: source_get_data(req_frames)
Src-->>Mod: Provides read_ptr, available_frames
Mod->>Snk: sink_get_buffer(req_frames)
Snk-->>Mod: Provides write_ptr, free_frames
note over Mod: Execute DSP Algorithm <br> (Read from read_ptr -> Write to write_ptr)
Mod->>Src: source_release_data(consumed_frames)
Mod->>Snk: sink_commit_buffer(produced_frames)

Copilot uses AI. Check for mistakes.

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`.
Comment on lines +106 to +110
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function references look incomplete/ambiguous: source_get_data_s16/sink_get_buffer_s16 are missing () and the 's32' variant is not named (it reads like a suffix rather than an API). Please spell out the exact function names for both widths (and include parentheses) so readers can grep/click them reliably.

Suggested change
- 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`.
- modules request data by calling `source_get_data_s16()` or `source_get_data_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 `sink_get_buffer_s32()`.

Copilot uses AI. Check for mistakes.
- 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.
Loading