From 5c1118265d7fd04bbbc30fd9aa6ffe89793c6728 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Fri, 25 Sep 2026 22:44:42 +0100 Subject: [PATCH] pipeline: introduce declarative static topology definition and loader Add support for declarative static audio pipelines in Sound Open Firmware. In microcontroller, hostless, and standalone embedded environments (such as ESP32-P4/S3/C6, PJRC Teensy 4.1, Nordic nRF54, RP2350, smart speakers, and audio bridges), there is no dynamic IPC host driver (Linux ALSA/SoundWire or Windows) to build topologies at runtime. This introduces: 1. Declarative topology structures, capability flags, and constructor macros in with full Doxygen docblocks covering all structures, fields, enums, macros, and public APIs. Complete sample rate masks (8k through 384k) aligned with SOF_RATE_* and frame format masks aligned with enum sof_ipc_frame. 2. Ops-driven static module architecture: generic headers and loader engine are 100% decoupled from individual audio processing components. Audio processing modules define and register their own operations (struct sof_static_module_ops) providing .create, .apply_volume, .apply_switch, and .apply_enum callbacks directly in their own source files (SRC, ASRC, Volume/Gain, Level Multiplier, Selector, EQ IIR, DRC, and TDFB) using DECLARE_STATIC_MODULE_OPS(). 3. Generic module operations registry and base IPC4 config synthesizer in static_pipeline_modules.c: completely generic infrastructure for ops registration, lookup with error logging, base configuration synthesis with macros for IPC4 channel maps, and default fallback component instantiation. 4. Concise, generic constructor macros (SOF_STATIC_MODULE, SOF_STATIC_MODULE_RATE_CONV, SOF_STATIC_ENDPOINT_DAI, and SOF_STATIC_ENDPOINT_USB) allowing clean, compact topology definitions for any audio processing module and DAI endpoint. 5. Modular IPC configuration and base config synthesizer: synthesizes standard IPC4 base module configurations (struct ipc4_base_module_cfg) on behalf of the absent host, calculating input and output buffer sizes (IBS/OBS) dynamically from component capabilities and pipeline periods. 6. Dynamic multi-rate, multi-channel, and format negotiation: eliminates hardcoded sample rates and channel counts in buffer initialization and pipeline/component prepare, supporting sample rate converters (SRC/ASRC), up/down mixers, and format conversions with per-buffer rate/channel inheritance. 7. Generic static topology loader engine in static_pipeline_loader.c to parse and instantiate native SOF components, buffers, routes, and kcontrols directly at boot. 8. Component preparation and parameter negotiation to ensure all processing modules reach ready state prior to streaming. 9. Direct pipeline start, stop, and trigger control with separate start/stop execution paths (sof_static_pipeline_start, sof_static_pipeline_stop), strict error checking preventing triggers on prepare failures, state validation, and copy task management. 10. Decoupled custom kcontrol callback handler to support platform- and board-specific controls without polluting core audio modules. 11. CONFIG_STATIC_PIPELINE Kconfig option and CMakeLists.txt integration. 12. Comprehensive developer documentation, architecture overview, and step-by-step tutorial with multi-rate SRC examples in src/audio/pipeline/README.md. Signed-off-by: Liam Girdwood --- src/audio/Kconfig | 10 + src/audio/asrc/asrc.c | 35 + src/audio/component.c | 23 + src/audio/drc/drc.c | 26 + src/audio/eq_iir/eq_iir.c | 39 + src/audio/level_multiplier/level_multiplier.c | 42 + src/audio/pipeline/CMakeLists.txt | 6 + src/audio/pipeline/README.md | 242 +++- src/audio/pipeline/static_pipeline_loader.c | 1282 +++++++++++++++++ src/audio/pipeline/static_pipeline_modules.c | 240 +++ src/audio/pipeline/static_pipeline_modules.h | 45 + src/audio/pipeline/static_pipeline_uac2.c | 189 +++ src/audio/selector/selector.c | 70 + src/audio/src/src.c | 31 + src/audio/tdfb/tdfb.c | 26 + src/audio/volume/volume.c | 99 ++ src/include/sof/audio/component.h | 13 + .../sof/audio/pipeline/static_pipeline.h | 807 +++++++++++ 18 files changed, 3224 insertions(+), 1 deletion(-) create mode 100644 src/audio/pipeline/static_pipeline_loader.c create mode 100644 src/audio/pipeline/static_pipeline_modules.c create mode 100644 src/audio/pipeline/static_pipeline_modules.h create mode 100644 src/audio/pipeline/static_pipeline_uac2.c create mode 100644 src/include/sof/audio/pipeline/static_pipeline.h diff --git a/src/audio/Kconfig b/src/audio/Kconfig index 8accb25738a2..777341fe8a84 100644 --- a/src/audio/Kconfig +++ b/src/audio/Kconfig @@ -51,6 +51,16 @@ config HOST_DMA_IPC_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 STATIC_PIPELINE + bool "Declarative Static Audio Pipeline Loader" + default n + help + Enable support for declarative static audio pipelines in Sound + Open Firmware. This provides a compile-time and runtime loader + engine for hostless, microcontroller, and standalone embedded + targets that do not rely on dynamic IPC topology messages from + a host operating system. + config COMP_CHAIN_DMA bool "Chain DMA component" depends on IPC_MAJOR_4 diff --git a/src/audio/asrc/asrc.c b/src/audio/asrc/asrc.c index f75009ffd775..78e6041b0d94 100644 --- a/src/audio/asrc/asrc.c +++ b/src/audio/asrc/asrc.c @@ -846,4 +846,39 @@ SOF_LLEXT_BUILDINFO; DECLARE_MODULE_ADAPTER(asrc_interface, ASRC_UUID, asrc_tr); SOF_MODULE_INIT(asrc, sys_comp_module_asrc_interface_init); +#if CONFIG_STATIC_PIPELINE +#include + +#if CONFIG_IPC_MAJOR_4 +#include "asrc_ipc4.h" + +static struct comp_dev *asrc_static_create(const struct comp_driver *drv, + struct comp_ipc_config *cfg, + const struct sof_static_comp *cdesc, + uint32_t period_us) +{ + struct ipc4_asrc_module_cfg asrc_cfg; + + memset(&asrc_cfg, 0, sizeof(asrc_cfg)); + sof_static_init_base_cfg(&asrc_cfg.base, cdesc, period_us); + asrc_cfg.out_freq = (enum ipc4_sampling_frequency)(cdesc->caps.sink_rate ? + cdesc->caps.sink_rate : + cdesc->caps.default_rate); + + struct ipc_config_process spec = { + .size = sizeof(asrc_cfg), + .data = (const uint8_t *)&asrc_cfg, + }; + return drv->ops.create(drv, cfg, &spec); +} + +static struct sof_static_module_ops asrc_static_ops = { + .uuid = &ASRC_UUID, + .create = asrc_static_create, +}; + +DECLARE_STATIC_MODULE_OPS(asrc, &asrc_static_ops); +#endif /* CONFIG_IPC_MAJOR_4 */ +#endif /* CONFIG_STATIC_PIPELINE */ + #endif diff --git a/src/audio/component.c b/src/audio/component.c index f798e0a971e0..ff68192018ea 100644 --- a/src/audio/component.c +++ b/src/audio/component.c @@ -100,6 +100,29 @@ int comp_set_adapter_ops(const struct comp_driver *drv, const struct module_inte return -ENODEV; } +const struct comp_driver *comp_driver_find(const struct sof_uuid *uuid, uint32_t type) +{ + struct comp_driver_list *drivers = comp_drivers_get(); + struct list_item *clist; + + if (!drivers) + return NULL; + + list_for_item(clist, &drivers->list) { + struct comp_driver_info *info = container_of(clist, struct comp_driver_info, list); + + /* Match by UUID if provided (used for audio processing modules) */ + if (uuid && info->drv->uid && !memcmp(info->drv->uid, uuid, UUID_SIZE)) + return info->drv; + + /* Match by driver type if no UUID is specified (used for Host and DAI endpoints) */ + if (!uuid && info->drv->type == type) + return info->drv; + } + + return NULL; +} + /* NOTE: Keep the component state diagram up to date: * sof-docs/developer_guides/firmware/components/images/comp-dev-states.pu */ diff --git a/src/audio/drc/drc.c b/src/audio/drc/drc.c index 8709d9abcb4b..b20c1d9f25e5 100644 --- a/src/audio/drc/drc.c +++ b/src/audio/drc/drc.c @@ -481,4 +481,30 @@ SOF_LLEXT_BUILDINFO; DECLARE_MODULE_ADAPTER(drc_interface, drc_uuid, drc_tr); SOF_MODULE_INIT(drc, sys_comp_module_drc_interface_init); +#if CONFIG_STATIC_PIPELINE +#include + +static int drc_static_apply_switch(struct comp_dev *dev, uint32_t channels, int32_t val) +{ + struct processing_module *mod = comp_mod(dev); + + if (!mod) + return -EINVAL; + + struct drc_comp_data *cd = module_get_private_data(mod); + + if (cd) + cd->enable_switch = (val != 0); + + return 0; +} + +static struct sof_static_module_ops drc_static_ops = { + .uuid = &drc_uuid, + .apply_switch = drc_static_apply_switch, +}; + +DECLARE_STATIC_MODULE_OPS(drc, &drc_static_ops); +#endif /* CONFIG_STATIC_PIPELINE */ + #endif diff --git a/src/audio/eq_iir/eq_iir.c b/src/audio/eq_iir/eq_iir.c index a9a4bc4aa24a..90547b4bec78 100644 --- a/src/audio/eq_iir/eq_iir.c +++ b/src/audio/eq_iir/eq_iir.c @@ -258,4 +258,43 @@ DECLARE_TR_CTX(eq_iir_tr, SOF_UUID(eq_iir_uuid), LOG_LEVEL_INFO); DECLARE_MODULE_ADAPTER(eq_iir_interface, eq_iir_uuid, eq_iir_tr); SOF_MODULE_INIT(eq_iir, sys_comp_module_eq_iir_interface_init); +#if CONFIG_STATIC_PIPELINE +#include + +static int eq_iir_static_apply_switch(struct comp_dev *dev, uint32_t channels, int32_t val) +{ + struct processing_module *mod = comp_mod(dev); + + if (!mod) + return -EINVAL; + + struct comp_data *cd = module_get_private_data(mod); + + if (cd) { + if (val == 0) { + cd->eq_iir_func = eq_iir_pass; + } else if (cd->iir_delay_size) { +#if CONFIG_FORMAT_FLOAT + struct comp_buffer *sourceb = comp_dev_get_first_data_producer(dev); + + if (sourceb && audio_stream_get_frm_fmt(&sourceb->stream) == SOF_IPC_FRAME_FLOAT) + cd->eq_iir_func = eq_iir_float_default; + else + cd->eq_iir_func = eq_iir_s16_default; +#else + cd->eq_iir_func = eq_iir_s16_default; +#endif + } + } + return 0; +} + +static struct sof_static_module_ops eq_iir_static_ops = { + .uuid = &eq_iir_uuid, + .apply_switch = eq_iir_static_apply_switch, +}; + +DECLARE_STATIC_MODULE_OPS(eq_iir, &eq_iir_static_ops); +#endif /* CONFIG_STATIC_PIPELINE */ + #endif diff --git a/src/audio/level_multiplier/level_multiplier.c b/src/audio/level_multiplier/level_multiplier.c index 4fca482ced72..51177298983b 100644 --- a/src/audio/level_multiplier/level_multiplier.c +++ b/src/audio/level_multiplier/level_multiplier.c @@ -204,4 +204,46 @@ DECLARE_TR_CTX(level_multiplier_tr, SOF_UUID(level_multiplier_uuid), LOG_LEVEL_I DECLARE_MODULE_ADAPTER(level_multiplier_interface, level_multiplier_uuid, level_multiplier_tr); SOF_MODULE_INIT(level_multiplier, sys_comp_module_level_multiplier_interface_init); +#if CONFIG_STATIC_PIPELINE +#include + +static int lm_static_apply_volume(struct comp_dev *dev, uint32_t channels, int32_t val) +{ + struct processing_module *mod = comp_mod(dev); + + if (!mod) + return -EINVAL; + + struct level_multiplier_comp_data *cd = module_get_private_data(mod); + + if (cd) + cd->gain = val; + + return 0; +} + +static int lm_static_apply_switch(struct comp_dev *dev, uint32_t channels, int32_t val) +{ + struct processing_module *mod = comp_mod(dev); + + if (!mod) + return -EINVAL; + + struct level_multiplier_comp_data *cd = module_get_private_data(mod); + + if (cd) + cd->gain = (val == 0) ? 0 : LEVEL_MULTIPLIER_GAIN_ONE; + + return 0; +} + +static struct sof_static_module_ops lm_static_ops = { + .uuid = &level_multiplier_uuid, + .apply_volume = lm_static_apply_volume, + .apply_switch = lm_static_apply_switch, +}; + +DECLARE_STATIC_MODULE_OPS(level_multiplier, &lm_static_ops); +#endif /* CONFIG_STATIC_PIPELINE */ + #endif diff --git a/src/audio/pipeline/CMakeLists.txt b/src/audio/pipeline/CMakeLists.txt index cb26dc3214bd..eca2f06857d0 100644 --- a/src/audio/pipeline/CMakeLists.txt +++ b/src/audio/pipeline/CMakeLists.txt @@ -7,3 +7,9 @@ add_local_sources(sof pipeline-xrun.c pipeline-schedule.c ) + +add_local_sources_ifdef(CONFIG_STATIC_PIPELINE sof + static_pipeline_loader.c + static_pipeline_modules.c + static_pipeline_uac2.c +) diff --git a/src/audio/pipeline/README.md b/src/audio/pipeline/README.md index 4e530bdc1463..35e4966d0f0e 100644 --- a/src/audio/pipeline/README.md +++ b/src/audio/pipeline/README.md @@ -240,6 +240,246 @@ sequenceDiagram deactivate Sched ``` +## Static Pipeline Subsystem (Hostless / Standalone Execution) + +In microcontroller, hostless, or standalone embedded environments (e.g. ESP32-P4/S3/C6, Teensy 4.1 / i.MX RT1062, Nordic nRF54, RP2350, audio bridge appliances, smart speakers, standalone DSP dongles), there is no dynamic IPC host (Linux ALSA/SoundWire or Windows driver) to construct audio topologies at runtime. + +The Static Pipeline subsystem (`CONFIG_STATIC_PIPELINE`) provides a declarative C API (``) and loader engine (`static_pipeline_loader.c`) that instantiates full SOF processing graphs directly at boot: + +```mermaid +graph TD + Topo["Declarative Topology Descriptor
(struct sof_static_topology)"] --> Loader["Static Topology Loader
(sof_static_topology_init)"] + Loader --> PPL["Native Pipelines
(pipeline_new)"] + Loader --> COMPS["Components
(DAI, USB/Host, Modules)"] + Loader --> BUFS["Intermediate Buffers
(buffer_alloc)"] + Loader --> ROUTES["Graph Connections
(pipeline_connect)"] + Loader --> CONTROLS["Kcontrols & Custom Callbacks
(Volume, Mute, EQ Bypass, Routing)"] +``` + +### Key Capabilities & Architecture + +1. **Declarative Definitions**: Topologies describe pipelines, components (modules, DAI, USB/Host endpoints), intermediate buffers, routes, PCMs, and kcontrols using builder macros (`SOF_STATIC_MODULE`, `SOF_STATIC_ENDPOINT_DAI`, `SOF_STATIC_ENDPOINT_USB`, `SOF_STATIC_BUFFER`, `SOF_STATIC_ROUTE`, `SOF_STATIC_KCONTROL_*`). +2. **Ops-Driven Module Architecture**: Generic headers and the loader engine are completely decoupled from individual audio modules. Each audio processing component defines and registers operations (`struct sof_static_module_ops`) providing `.create`, `.apply_volume`, `.apply_switch`, and `.apply_enum` callbacks directly in its own module source file via `DECLARE_STATIC_MODULE_OPS()`. +3. **Explicit Parameter Initialization**: Synchronously negotiates stream parameters and prepares all graph components (`comp_params`, `comp_prepare`) to bring non-host-driven pipelines into ready state before audio streaming begins. +4. **Trigger & State Synchronization**: Provides direct runtime pipeline control (`sof_static_pipeline_trigger()`, `sof_static_pipeline_trigger_by_uac2_term()`) with state validation, synchronous trigger execution, per-component trigger propagation, and scheduler copy task activation/cancellation. +5. **Decoupled Control Callbacks**: Standard kcontrols dispatch directly to module ops (Volume, Mute, Level Multiplier, EQ bypass, DRC compression switch, Selector channel), while custom platform or board-level controls (clock mode switching, hardware routing, DMIC injectors) are handled cleanly via `custom_control_handler` callbacks. + +### Usage Guide & Tutorial + +#### 1. Defining Components + +Declare processing modules, host/USB endpoints, and hardware DAIs using either concise constructor macros or explicit struct declarations: + +```c +#include + +extern const struct sof_uuid src_uuid; +extern const struct sof_uuid volume_uuid; +extern const struct sof_uuid eq_iir_uuid; + +static const struct sof_static_comp s_comps[] = { + /* Playback Endpoint (USB UAC2 Terminal ID 1) @ 44.1 kHz */ + SOF_STATIC_ENDPOINT_USB( + 1, 1, "USB_PB", SOF_IPC_STREAM_PLAYBACK, + SOF_IPC_FRAME_S16_LE, 44100, 2, 1 + ), + /* Sample Rate Converter: 44.1 kHz In -> 48 kHz Out */ + SOF_STATIC_MODULE_RATE_CONV( + 2, 1, "SRC_PB", SOF_IPC_STREAM_PLAYBACK, &src_uuid, + SOF_IPC_FRAME_S16_LE, 44100, 48000, 2 + ), + /* Volume Control Module @ 48 kHz */ + SOF_STATIC_MODULE( + 3, 1, "VOL_PB", SOF_IPC_STREAM_PLAYBACK, &volume_uuid, + SOF_IPC_FRAME_S16_LE, 48000, 2, NULL, 0 + ), + /* 4-Band Parametric IIR EQ Module @ 48 kHz */ + SOF_STATIC_MODULE( + 4, 1, "EQ_PB", SOF_IPC_STREAM_PLAYBACK, &eq_iir_uuid, + SOF_IPC_FRAME_S16_LE, 48000, 2, NULL, 0 + ), + /* Hardware I2S DAI Output @ 48 kHz */ + SOF_STATIC_ENDPOINT_DAI( + 5, 1, "I2S_TX", SOF_IPC_STREAM_PLAYBACK, + SOF_IPC_FRAME_S16_LE, 48000, 2, + SOF_DAI_INTEL_SSP, 0, SOF_DAI_FMT_I2S | SOF_DAI_FMT_CBC_CFC + ), +}; +``` + +#### 2. Defining Intermediate Buffers + +Allocate audio buffers connecting consecutive processing blocks. In multi-rate pipelines (such as with SRC / ASRC), buffers can declare their individual sample rates and channel counts or inherit them dynamically from the upstream producer: + +```c +static const struct sof_static_buffer s_buffers[] = { + /* Buffer between USB_PB (1) and SRC_PB (2) @ 44.1 kHz */ + SOF_STATIC_BUFFER( + .id = 1, + .size = 45 * 2 * sizeof(int16_t) * 4, /* 4 periods @ 44.1 kHz */ + .fmt = SOF_IPC_FRAME_S16_LE, + .rate = 44100, + .channels = 2, + ), + /* Buffer between SRC_PB (2) and VOL_PB (3) @ 48 kHz */ + SOF_STATIC_BUFFER( + .id = 2, + .size = 48 * 2 * sizeof(int16_t) * 4, /* 4 periods @ 48 kHz */ + .fmt = SOF_IPC_FRAME_S16_LE, + .rate = 48000, + .channels = 2, + ), + /* Buffer between VOL_PB (3) and EQ_PB (4) @ 48 kHz */ + SOF_STATIC_BUFFER( + .id = 3, + .size = 48 * 2 * sizeof(int16_t) * 4, + .fmt = SOF_IPC_FRAME_S16_LE, + .rate = 48000, + .channels = 2, + ), + /* Buffer between EQ_PB (4) and I2S_TX (5) @ 48 kHz */ + SOF_STATIC_BUFFER( + .id = 4, + .size = 48 * 2 * sizeof(int16_t) * 4, + .fmt = SOF_IPC_FRAME_S16_LE, + .rate = 48000, + .channels = 2, + ), +}; +``` + +#### 3. Defining Graph Routes + +Connect component outputs through intermediate buffers to component inputs: + +```c +static const struct sof_static_route s_routes[] = { + SOF_STATIC_ROUTE(.src_comp_id = 1, .buffer_id = 1, .sink_comp_id = 2), + SOF_STATIC_ROUTE(.src_comp_id = 2, .buffer_id = 2, .sink_comp_id = 3), + SOF_STATIC_ROUTE(.src_comp_id = 3, .buffer_id = 3, .sink_comp_id = 4), + SOF_STATIC_ROUTE(.src_comp_id = 4, .buffer_id = 4, .sink_comp_id = 5), +}; +``` + +#### 4. Defining Top-Level Pipelines + +Specify pipeline execution properties, scheduling domains, and boundary endpoints: + +```c +static const struct sof_static_pipeline_desc s_pipelines[] = { + { + .pipeline_id = 1, + .name = "Playback Pipeline", + .direction = SOF_IPC_STREAM_PLAYBACK, + .priority = 0, + .core = 0, + .period = 1000, /* 1 ms tick */ + .frames_per_sched = 48, /* 48 frames per ms @ 48 kHz */ + .time_domain = SOF_TIME_DOMAIN_TIMER, + .default_rate = 48000, /* Pipeline target rate */ + .default_channels = 2, + .sched_comp_id = 5, /* Driven by I2S DAI */ + .source_comp_id = 1, /* Ingress: USB_PB */ + .sink_comp_id = 5, /* Egress: I2S_TX */ + }, +}; +``` + +#### 5. Defining Kcontrols & Custom Handlers + +Declare runtime controls (volume, mute, filter bypass switches) and optional platform callbacks: + +```c +static const struct sof_static_kcontrol s_controls[] = { + SOF_STATIC_KCONTROL_VOLUME( + .id = 1, + .name = "Master Playback Volume", + .target_comp_id = 3, /* Targets VOL_PB (3) */ + .min = 0, + .max = 65536, + .def = 65536, /* 0 dB default */ + .channels = 2, + .uac2_entity_id = 10, /* Bound to UAC2 Feature Unit 10 */ + ), + SOF_STATIC_KCONTROL_SWITCH( + .id = 2, + .name = "EQ Bypass Switch", + .target_comp_id = 4, /* Targets EQ_PB (4, 0=bypass, 1=active) */ + .min = 0, + .max = 1, + .def = 1, + .channels = 1, + ), + SOF_STATIC_KCONTROL_SWITCH( + .id = 3, + .name = "Hardware Clock Mode", + .target_comp_id = 0, /* Custom control (dispatched to callback) */ + .min = 0, + .max = 1, + .def = 1, /* 1 = Master, 0 = Slave */ + .channels = 1, + ), +}; + +static int platform_control_callback(const struct sof_static_kcontrol *ctl, + int32_t val, void *priv) +{ + if (ctl->id == 3) { + /* Reconfigure physical I2S clock mode between Master and Slave */ + return platform_set_i2s_clock_mode(val == 1); + } + return -EINVAL; +} + +const struct sof_static_topology g_my_platform_topology = { + .name = "Demo Playback Topology", + .num_pipelines = ARRAY_SIZE(s_pipelines), + .pipelines = s_pipelines, + .num_comps = ARRAY_SIZE(s_comps), + .comps = s_comps, + .num_buffers = ARRAY_SIZE(s_buffers), + .buffers = s_buffers, + .num_routes = ARRAY_SIZE(s_routes), + .routes = s_routes, + .num_controls = ARRAY_SIZE(s_controls), + .controls = s_controls, + .custom_control_handler = platform_control_callback, + .custom_control_data = NULL, +}; +``` + +#### 6. Instantiating and Running the Topology + +At board startup, call `sof_static_topology_init()`: + +```c +#include + +int my_platform_init(void) +{ + /* Build and prepare audio pipeline graph */ + int ret = sof_static_topology_init(&g_my_platform_topology); + if (ret < 0) { + LOG_ERR("Failed to initialize static topology: %d", ret); + return ret; + } + + /* Start audio streaming */ + sof_static_pipeline_trigger(1, true); + return 0; +} +``` + +#### 7. Runtime Control APIs + +- **`sof_static_kcontrol_set(uint32_t ctrl_id, int32_t val)`**: Updates volume, mute, or bypass state at runtime. +- **`sof_static_kcontrol_get(uint32_t ctrl_id, int32_t *val)`**: Reads current control value. +- **`sof_static_pipeline_start(uint32_t pipeline_id)`**: Prepares and starts a pipeline synchronously. +- **`sof_static_pipeline_stop(uint32_t pipeline_id)`**: Stops and pauses an active pipeline synchronously. +- **`sof_static_pipeline_trigger(uint32_t pipeline_id, bool start)`**: Starts or stops a pipeline synchronously. +- **`sof_static_kcontrol_set_by_uac2(uint8_t entity_id, uint8_t ch, int32_t val, bool is_volume)`**: Automatically translates USB UAC2 8.8 dB fader commands to SOF native volume values. + ## Configuration and Scripts -* **CMakeLists.txt**: Straightforward build configuration integrating the fundamental internal execution blocks of the SOF graph: `pipeline-graph.c`, `pipeline-stream.c`, `pipeline-params.c`, `pipeline-xrun.c`, and `pipeline-schedule.c`. +* **CMakeLists.txt**: Build configuration integrating internal execution blocks of the SOF graph (`pipeline-graph.c`, `pipeline-stream.c`, `pipeline-params.c`, `pipeline-xrun.c`, `pipeline-schedule.c`) and conditionally compiling `static_pipeline_loader.c`, `static_pipeline_modules.c`, and `static_pipeline_uac2.c` when `CONFIG_STATIC_PIPELINE=y`. +* **Endpoint Callbacks**: Hardware DAI endpoints can attach optional platform-specific clock and hardware configuration callbacks via `SOF_STATIC_ENDPOINT_DAI_CFG()` without polluting the generic loader with SoC-specific code. diff --git a/src/audio/pipeline/static_pipeline_loader.c b/src/audio/pipeline/static_pipeline_loader.c new file mode 100644 index 000000000000..9e111a096075 --- /dev/null +++ b/src/audio/pipeline/static_pipeline_loader.c @@ -0,0 +1,1282 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2026 Sound Open Firmware (SOF) Project + */ + +/** + * \file audio/pipeline/static_pipeline_loader.c + * \brief Generic Static Audio Topology Loader Engine + * \author Liam Girdwood + * + * Implements the core runtime engine for declarative static audio pipelines. + * In microcontroller, hostless, and standalone embedded environments, this engine + * instantiates SOF components, buffers, routes, and kcontrols directly at boot + * without dynamic IPC topology commands from a host operating system. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "static_pipeline_modules.h" + +LOG_MODULE_REGISTER(static_pipeline_loader, CONFIG_SOF_LOG_LEVEL); + +/* + * Maximum capacity limits for static topology tracking tables. + * Sized generously to support complex embedded multi-channel topologies + * while avoiding dynamic heap overhead during runtime lookups. + */ +#define MAX_STATIC_PIPELINES 16 +#define MAX_STATIC_COMPS 64 +#define MAX_STATIC_BUFFERS 64 +#define MAX_STATIC_CONTROLS 64 + +/* Active declarative topology descriptor */ +static const struct sof_static_topology *s_active_topo; + +/* Static tracking tables for fast ID-to-pointer lookups */ +static struct pipeline *s_pipelines[MAX_STATIC_PIPELINES]; +static uint32_t s_pipeline_ids[MAX_STATIC_PIPELINES]; +static size_t s_num_pipelines; + +static struct comp_dev *s_comps[MAX_STATIC_COMPS]; +static uint32_t s_comp_ids[MAX_STATIC_COMPS]; +static size_t s_num_comps; + +static struct comp_buffer *s_buffers[MAX_STATIC_BUFFERS]; +static uint32_t s_buffer_ids[MAX_STATIC_BUFFERS]; +static size_t s_num_buffers; + +/* Cached kcontrol values */ +static int32_t s_control_vals[MAX_STATIC_CONTROLS]; + +/* + * Primary stream sample rate in Hz. Initialized to 0 (unassigned) and + * dynamically resolved from the declarative topology descriptor at boot + * (or updated at runtime via sof_static_set_sample_rate()). + */ +static uint32_t s_sample_rate; + +/** + * \brief Send initial binary configuration blob (e.g. EQ coefficients, DRC tuning) to a component. + * + * In hostless environments, modules requiring configuration tables (such as equalizer + * filter coefficients or DRC curves) must be configured directly at boot. This routine + * dispatches the initial configuration blob via the module adapter's set_configuration ops. + * + * \param[in] dev Target component device pointer. + * \param[in] abi_blob Pointer to ABI configuration blob starting with struct sof_abi_hdr. + * \param[in] abi_blob_total_size Total size of blob in bytes including header. + * \return 0 on success, negative errno on failure. + */ +static int send_comp_config(struct comp_dev *dev, const void *abi_blob, size_t abi_blob_total_size) +{ + /* Validate component pointer and configuration blob payload */ + if (!dev || !abi_blob || abi_blob_total_size == 0) + return -EINVAL; + + const struct sof_abi_hdr *hdr = (const struct sof_abi_hdr *)abi_blob; + struct processing_module *mod = comp_mod(dev); + + /* Verify the component's module adapter driver provides set_configuration */ + if (!mod || !mod->dev || !mod->dev->drv || !mod->dev->drv->adapter_ops || + !mod->dev->drv->adapter_ops->set_configuration) { + LOG_ERR("No adapter_ops set_configuration for comp %d", dev->ipc_config.id); + return -EINVAL; + } + +#if CONFIG_IPC_MAJOR_4 + /* + * In IPC4 mode, pass the raw configuration payload directly following the + * ABI header to the module adapter's set_configuration callback. + */ + const uint8_t *raw_data = (const uint8_t *)abi_blob + sizeof(struct sof_abi_hdr); + int ret = mod->dev->drv->adapter_ops->set_configuration(mod, 0, MODULE_CFG_FRAGMENT_SINGLE, + hdr->size, raw_data, + hdr->size, NULL, 0); +#else + /* + * In IPC3 mode, wrap the configuration payload in a struct sof_ipc_ctrl_data + * with command SOF_CTRL_CMD_BINARY as expected by IPC3 processing components. + */ + size_t cdata_size = sizeof(struct sof_ipc_ctrl_data) + sizeof(struct sof_abi_hdr) + hdr->size; + struct sof_ipc_ctrl_data *cdata = rzalloc(SOF_MEM_FLAG_USER, cdata_size); + if (!cdata) { + LOG_ERR("Failed to allocate ctrl_data size %zu", cdata_size); + return -ENOMEM; + } + + cdata->cmd = SOF_CTRL_CMD_BINARY; + cdata->num_elems = hdr->size; + cdata->data[0].magic = hdr->magic; + cdata->data[0].type = hdr->type; + cdata->data[0].size = hdr->size; + cdata->data[0].abi = hdr->abi; + memcpy_s(cdata->data[0].data, hdr->size, (const uint8_t *)abi_blob + sizeof(struct sof_abi_hdr), hdr->size); + + int ret = mod->dev->drv->adapter_ops->set_configuration(mod, 0, MODULE_CFG_FRAGMENT_SINGLE, + hdr->size, (const uint8_t *)cdata, + hdr->size, NULL, 0); + rfree(cdata); +#endif + + /* Log configuration status for diagnostics */ + if (ret < 0) { + LOG_ERR("set_configuration failed for comp %d: ret %d", dev->ipc_config.id, ret); + } else { + LOG_INF("Configuration blob loaded for comp %d (%u bytes)", dev->ipc_config.id, hdr->size); + } + + return ret; +} + +/** + * \brief Initialize audio stream and buffer parameters for an intermediate connection buffer. + * + * Configures the IPC stream parameters (sampling rate, channel count, frame format, + * container sizes, valid bit depth, host period bytes, and ALSA channel map) and + * synchronizes the underlying audio stream (buf->stream) as well as its associated + * sink and source interfaces. + * + * Intermediate circular buffers connect adjacent processing components in the + * graph. Initializing their stream parameters and reset state ensures that + * buffer_set_params() and sink/source getters establish identical format + * expectations prior to component parameter negotiation and streaming. + * + * \param[in,out] buf Pointer to allocated intermediate component buffer. + * \param[in] dir Audio stream direction (SOF_IPC_STREAM_PLAYBACK or SOF_IPC_STREAM_CAPTURE). + * \param[in] fmt PCM audio frame format (enum sof_ipc_frame). + * \param[in] rate Audio sampling rate in Hz. + * \param[in] channels Audio channel count. + */ +static void init_buffer_params(struct comp_buffer *buf, uint32_t dir, enum sof_ipc_frame fmt, + uint32_t rate, uint32_t channels) +{ + if (!buf) + return; + + /* Calculate sample container and valid byte sizes based on the PCM format */ + uint32_t sample_bytes = get_sample_bytes(fmt); + uint32_t valid_bytes = get_sample_bitdepth(fmt) / 8; + + /* Number of audio frames per 1 millisecond scheduling period */ + uint32_t frames = rate / 1000; + + /* Populate standard IPC stream parameters structure */ + struct sof_ipc_stream_params params; + memset(¶ms, 0, sizeof(params)); + params.rate = rate; + params.channels = channels; + params.frame_fmt = fmt; + params.sample_container_bytes = sample_bytes; + params.sample_valid_bytes = valid_bytes; + params.buffer_fmt = SOF_IPC_BUFFER_INTERLEAVED; + params.host_period_bytes = frames * channels * sample_bytes; + params.direction = dir; + + /* Populate standard channel mapping for mono, stereo, and multi-channel streams */ + for (uint32_t c = 0; c < SOF_IPC_MAX_CHANNELS; c++) { + if (c == 0) + params.chmap[c] = (channels == 1) ? SOF_CHMAP_MONO : SOF_CHMAP_FL; + else if (c == 1 && channels > 1) + params.chmap[c] = SOF_CHMAP_FR; + else if (c < channels) + params.chmap[c] = c + 1; + else + params.chmap[c] = SOF_CHMAP_NA; + } + + /* Apply stream parameters to buffer structure */ + buffer_set_params(buf, ¶ms, BUFFER_UPDATE_FORCE); + + /* Configure circular audio stream parameters and byte alignment */ + audio_stream_set_valid_fmt(&buf->stream, fmt); + audio_stream_set_rate(&buf->stream, rate); + audio_stream_set_channels(&buf->stream, channels); + audio_stream_set_frm_fmt(&buf->stream, fmt); + audio_stream_set_align(SOF_FRAME_BYTE_ALIGN, sample_bytes, &buf->stream); + audio_stream_reset(&buf->stream); + + /* Synchronize sink interface parameters so downstream consumers match */ + struct sof_sink *sink = audio_buffer_get_sink(&buf->audio_buffer); + if (sink) { + sink_set_valid_fmt(sink, fmt); + sink_set_rate(sink, rate); + sink_set_channels(sink, channels); + sink_set_frm_fmt(sink, fmt); + sink_set_alignment_constants(sink, SOF_FRAME_BYTE_ALIGN, sample_bytes); + } + + /* Synchronize source interface parameters so upstream producers match */ + struct sof_source *source = audio_buffer_get_source(&buf->audio_buffer); + if (source) { + source_set_valid_fmt(source, fmt); + source_set_rate(source, rate); + source_set_channels(source, channels); + source_set_frm_fmt(source, fmt); + source_set_alignment_constants(source, SOF_FRAME_BYTE_ALIGN, sample_bytes); + } +} + +/** + * \brief Resolve the primary stream sample rate from declarative topology. + * + * Scans pipelines and components for declared sample rates, falling back to + * standard 48 kHz if completely unspecified. + * + * \param[in] topo Topology descriptor pointer. + */ +static void sof_static_topology_resolve_rate(const struct sof_static_topology *topo) +{ + if (s_sample_rate) + return; + + /* First check if the primary pipeline declares a default rate */ + for (size_t p = 0; p < topo->num_pipelines; p++) { + if (topo->pipelines[p].default_rate) { + s_sample_rate = topo->pipelines[p].default_rate; + break; + } + } + + /* Fallback to checking the first component with a valid default rate */ + if (!s_sample_rate) { + for (size_t c = 0; c < topo->num_comps; c++) { + if (topo->comps[c].caps.default_rate) { + s_sample_rate = topo->comps[c].caps.default_rate; + break; + } + } + } + + /* If completely unspecified in topology, log and fall back to standard 48 kHz */ + if (!s_sample_rate) { + LOG_INF("Primary sample rate unspecified in topology, falling back to 48000 Hz"); + s_sample_rate = 48000; + } +} + +/** + * \brief Stage 1: Allocate top-level pipelines. + * + * Allocate pipeline objects first because components and buffers require an + * owning pipeline context for scheduling, buffer allocation, and clock domains. + * + * \param[in] topo Topology descriptor pointer. + * \return 0 on success, negative errno on failure. + */ +static int sof_static_topology_init_pipelines(const struct sof_static_topology *topo) +{ + for (size_t i = 0; i < topo->num_pipelines && i < MAX_STATIC_PIPELINES; i++) { + const struct sof_static_pipeline_desc *pdesc = &topo->pipelines[i]; + struct pipeline *pipe = pipeline_new(NULL, pdesc->pipeline_id, pdesc->priority, + pdesc->pipeline_id, NULL); + if (!pipe) { + LOG_ERR("Failed to allocate pipeline %u (%s)", pdesc->pipeline_id, pdesc->name); + return -ENOMEM; + } + + /* Configure pipeline scheduling parameters from declarative descriptor */ + pipe->pipeline_id = pdesc->pipeline_id; + pipe->period = pdesc->period ? pdesc->period : 1000; + pipe->frames_per_sched = pdesc->frames_per_sched ? pdesc->frames_per_sched : 48; + pipe->time_domain = pdesc->time_domain ? pdesc->time_domain : SOF_TIME_DOMAIN_TIMER; + + /* Record pipeline instance in static tracking array */ + s_pipelines[s_num_pipelines] = pipe; + s_pipeline_ids[s_num_pipelines] = pdesc->pipeline_id; + s_num_pipelines++; + + LOG_INF("Pipeline %u ('%s') initialized (period %u us, %u frames)", + pdesc->pipeline_id, pdesc->name ? pdesc->name : "", pipe->period, pipe->frames_per_sched); + } + + return 0; +} + +/** + * \brief Instantiate a hardware DAI endpoint component. + * + * \param[in] cdesc Component descriptor. + * \param[in] drv Component driver pointer. + * \param[in] cfg Standard component IPC configuration. + * \return Pointer to created comp_dev, or NULL on failure. + */ +static struct comp_dev *sof_static_init_dai_comp(const struct sof_static_comp *cdesc, + const struct comp_driver *drv, + const struct comp_ipc_config *cfg) +{ + /* Derive DAI sampling frequency from component caps or resolved rate */ + uint32_t dai_rate = cdesc->caps.default_rate ? cdesc->caps.default_rate : s_sample_rate; + struct ipc_config_dai dai_cfg = { + .type = cdesc->ep.dai.dai_type, + .dai_index = cdesc->ep.dai.dai_index, + .direction = cdesc->direction, + .sampling_frequency = dai_rate, + .dma_buffer_size = 1024, + .format = cdesc->ep.dai.format, + }; + struct comp_dev *dev = drv->ops.create(drv, cfg, &dai_cfg); + + if (!dev) + return NULL; + + struct sof_ipc_dai_config spec_cfg = { + .type = cdesc->ep.dai.dai_type, + .dai_index = cdesc->ep.dai.dai_index, + .format = cdesc->ep.dai.format, + }; + + /* Invoke optional platform/endpoint-specific configuration callback */ + if (cdesc->ep.dai.configure) { + int ret_cfg = cdesc->ep.dai.configure(dev, &dai_cfg, &spec_cfg); + if (ret_cfg < 0) { + LOG_ERR("DAI endpoint configure callback failed %d for comp %u", + ret_cfg, cdesc->id); + return NULL; + } + } + + /* Apply DAI configuration to driver data */ + struct dai_data *dd = comp_get_drvdata(dev); + + if (dd) { + comp_dai_config(dd, dev, &dai_cfg, &spec_cfg); + if (!dd->dai_spec_config) { + dd->dai_spec_config = sof_heap_alloc(dd->alloc_ctx.heap, + SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, + sizeof(struct sof_ipc_dai_config), 0); + if (dd->dai_spec_config) + memcpy(dd->dai_spec_config, &spec_cfg, sizeof(struct sof_ipc_dai_config)); + } + } + + /* Configure underlying physical DAI controller instance */ + struct dai *dai = dai_get(cdesc->ep.dai.dai_type, cdesc->ep.dai.dai_index, DAI_CREAT); + + if (dai) { + dai_set_config(dai, &dai_cfg, &spec_cfg, sizeof(spec_cfg)); + dai_put(dai); + } + + return dev; +} + +/** + * \brief Instantiate an audio processing module or host endpoint component. + * + * \param[in] cdesc Component descriptor. + * \param[in] drv Component driver pointer. + * \param[in] cfg Standard component IPC configuration. + * \param[in] topo Topology descriptor pointer. + * \return Pointer to created comp_dev, or NULL on failure. + */ +static struct comp_dev *sof_static_init_module_comp(const struct sof_static_comp *cdesc, + const struct comp_driver *drv, + const struct comp_ipc_config *cfg, + const struct sof_static_topology *topo) +{ + const struct sof_static_pipeline_desc *pdesc = NULL; + + for (size_t p = 0; p < topo->num_pipelines; p++) { + if (topo->pipelines[p].pipeline_id == cdesc->pipeline_id) { + pdesc = &topo->pipelines[p]; + break; + } + } + uint32_t period_us = (pdesc && pdesc->period) ? pdesc->period : 1000; + + /* Delegate instantiation to registered static ops or generic fallback */ + const struct sof_static_module_ops *ops = sof_static_find_module_ops(cdesc->uuid); + + if (ops && ops->create) + return ops->create(drv, cfg, cdesc, period_us); + + return sof_static_module_create_default(drv, cfg, cdesc, period_us); +} + +/** + * \brief Stage 2: Instantiate components. + * + * Create all declared audio processing modules, DAI endpoints, and host/USB + * interfaces. Look up drivers in the global SOF registry and initialize them. + * + * \param[in] topo Topology descriptor pointer. + * \return 0 on success, negative errno on failure. + */ +static int sof_static_topology_init_comps(const struct sof_static_topology *topo) +{ + for (size_t i = 0; i < topo->num_comps && i < MAX_STATIC_COMPS; i++) { + const struct sof_static_comp *cdesc = &topo->comps[i]; + uint32_t drv_type = (cdesc->type == SOF_STATIC_COMP_HOST) ? SOF_COMP_HOST : + (cdesc->type == SOF_STATIC_COMP_DAI) ? SOF_COMP_DAI : + SOF_COMP_MODULE_ADAPTER; + + /* Look up driver from component driver registry using standard API */ + const struct comp_driver *drv = comp_driver_find(cdesc->uuid, drv_type); + if (!drv) { + LOG_ERR("Component driver not found for comp %u ('%s')", cdesc->id, cdesc->name); + return -ENODEV; + } + + /* Populate standard SOF component IPC configuration */ + struct comp_ipc_config cfg = { + .id = cdesc->id, + .pipeline_id = cdesc->pipeline_id, + .core = 0, + .proc_domain = COMP_PROCESSING_DOMAIN_LL, + .frame_fmt = cdesc->caps.default_fmt, + .type = drv_type, + }; + + struct comp_dev *dev; + + if (cdesc->type == SOF_STATIC_COMP_DAI) + dev = sof_static_init_dai_comp(cdesc, drv, &cfg); + else + dev = sof_static_init_module_comp(cdesc, drv, &cfg, topo); + + if (!dev) { + LOG_ERR("Failed to create component %u ('%s')", cdesc->id, cdesc->name); + return -ENOMEM; + } + + /* Set component stream direction and owning pipeline */ + dev->direction = cdesc->direction; + dev->pipeline = sof_static_pipeline_get(cdesc->pipeline_id); + + /* Find owning pipeline descriptor to resolve scheduling period */ + const struct sof_static_pipeline_desc *pdesc = NULL; + for (size_t p = 0; p < topo->num_pipelines; p++) { + if (topo->pipelines[p].pipeline_id == cdesc->pipeline_id) { + pdesc = &topo->pipelines[p]; + break; + } + } + dev->period = (pdesc && pdesc->period) ? pdesc->period : 1000; + + /* Derive component frames per scheduling period */ + uint32_t comp_rate = cdesc->caps.default_rate ? cdesc->caps.default_rate : + ((pdesc && pdesc->default_rate) ? pdesc->default_rate : + s_sample_rate); + dev->frames = (comp_rate * dev->period) / 1000000; + if (dev->frames == 0) + dev->frames = 48; + + /* Send initial configuration blob if specified (EQ, DRC, etc.) */ + if (cdesc->init_blob && cdesc->init_blob_size > 0) + send_comp_config(dev, cdesc->init_blob, cdesc->init_blob_size); + + /* Record component instance in static tracking array */ + s_comps[s_num_comps] = dev; + s_comp_ids[s_num_comps] = cdesc->id; + s_num_comps++; + + LOG_INF("Component %u ('%s') created in pipeline %u", cdesc->id, cdesc->name, cdesc->pipeline_id); + } + + return 0; +} + +/** + * \brief Stage 3: Allocate audio buffers. + * + * Allocate intermediate circular buffers linking components. Each buffer inherits + * its rate, channels, format, and stream direction from its connected producer component. + * + * \param[in] topo Topology descriptor pointer. + * \return 0 on success, negative errno on failure. + */ +static int sof_static_topology_init_buffers(const struct sof_static_topology *topo) +{ + for (size_t i = 0; i < topo->num_buffers && i < MAX_STATIC_BUFFERS; i++) { + const struct sof_static_buffer *bdesc = &topo->buffers[i]; + struct comp_buffer *buf = buffer_alloc(NULL, bdesc->size, bdesc->flags, + PLATFORM_DCACHE_ALIGN, false); + if (!buf) { + LOG_ERR("Failed to allocate buffer %u (size %zu)", bdesc->id, bdesc->size); + return -ENOMEM; + } + + /* Resolve buffer parameters: rate, channels, format, and stream direction */ + uint32_t rate = bdesc->rate; + uint16_t channels = bdesc->channels; + enum sof_ipc_frame fmt = bdesc->fmt; + uint32_t dir = SOF_IPC_STREAM_PLAYBACK; + + /* Look up the producer component connected to this buffer via route table */ + const struct sof_static_comp *prod_comp = NULL; + for (size_t r = 0; r < topo->num_routes; r++) { + if (topo->routes[r].buffer_id == bdesc->id) { + uint32_t src_id = topo->routes[r].src_comp_id; + for (size_t c = 0; c < topo->num_comps; c++) { + if (topo->comps[c].id == src_id) { + prod_comp = &topo->comps[c]; + break; + } + } + break; + } + } + + /* Inherit parameters from producer component */ + if (prod_comp) { + dir = prod_comp->direction; + if (!fmt) + fmt = prod_comp->caps.default_fmt; + if (!rate) { + /* If producer is a rate converter (SRC / ASRC), inherit sink_rate */ + if (prod_comp->caps.sink_rate) + rate = prod_comp->caps.sink_rate; + else + rate = prod_comp->caps.default_rate; + } + if (!channels) + channels = prod_comp->caps.max_channels; + } + + /* Default fallbacks if unassigned */ + if (!rate) + rate = s_sample_rate; + if (!channels) + channels = 2; + if (!fmt) + fmt = SOF_IPC_FRAME_S16_LE; + + /* Initialize circular buffer stream parameters and reset pointers */ + init_buffer_params(buf, dir, fmt, rate, channels); + + /* Record buffer instance in static tracking array */ + s_buffers[s_num_buffers] = buf; + s_buffer_ids[s_num_buffers] = bdesc->id; + s_num_buffers++; + } + + return 0; +} + +/** + * \brief Stage 4: Connect pipeline graph routes. + * + * Establish graph connections: producer comp -> intermediate buffer -> consumer comp. + * + * \param[in] topo Topology descriptor pointer. + * \return 0 on success, negative errno on failure. + */ +static int sof_static_topology_init_routes(const struct sof_static_topology *topo) +{ + for (size_t i = 0; i < topo->num_routes; i++) { + const struct sof_static_route *r = &topo->routes[i]; + struct comp_dev *src = sof_static_comp_get(r->src_comp_id); + struct comp_buffer *buf = sof_static_buffer_get(r->buffer_id); + struct comp_dev *sink = sof_static_comp_get(r->sink_comp_id); + + /* Verify all route endpoints exist */ + if (!src || !buf || !sink) { + LOG_ERR("Failed route %u -> [buf %u] -> %u", r->src_comp_id, r->buffer_id, r->sink_comp_id); + return -EINVAL; + } + + /* Connect producer component to buffer and buffer to consumer component */ + pipeline_connect(src, buf, PPL_CONN_DIR_COMP_TO_BUFFER); + pipeline_connect(sink, buf, PPL_CONN_DIR_BUFFER_TO_COMP); + LOG_DBG("Connected: %u -> [buf %u] -> %u", r->src_comp_id, r->buffer_id, r->sink_comp_id); + } + + return 0; +} + +/** + * \brief Stage 5: Set pipeline endpoints and propagate parameters. + * + * Bind source, sink, and scheduling components to each pipeline. + * Propagate pipeline_params() and pipeline_prepare() to initiate graph-wide + * parameter negotiation across constituent components and buffers. + * + * \param[in] topo Topology descriptor pointer. + * \return 0 on success, negative errno on failure. + */ +static int sof_static_topology_init_pipeline_endpoints(const struct sof_static_topology *topo) +{ + for (size_t i = 0; i < topo->num_pipelines; i++) { + const struct sof_static_pipeline_desc *pdesc = &topo->pipelines[i]; + struct pipeline *pipe = sof_static_pipeline_get(pdesc->pipeline_id); + if (!pipe) + continue; + + /* Bind primary source, sink, and scheduling components */ + pipe->source_comp = sof_static_comp_get(pdesc->source_comp_id); + pipe->sink_comp = sof_static_comp_get(pdesc->sink_comp_id); + pipe->sched_comp = sof_static_comp_get(pdesc->sched_comp_id); + + /* Resolve the component driving pipeline parameters */ + struct comp_dev *host_or_sched = pipe->sched_comp ? pipe->sched_comp : pipe->source_comp; + if (host_or_sched) { + const struct sof_static_comp *cdesc = NULL; + for (size_t c = 0; c < topo->num_comps; c++) { + if (topo->comps[c].id == dev_comp_id(host_or_sched)) { + cdesc = &topo->comps[c]; + break; + } + } + + /* Determine stream sampling rate */ + uint32_t rate = pdesc->default_rate; + if (!rate && cdesc) + rate = cdesc->caps.default_rate; + if (!rate) + rate = s_sample_rate; + + /* Determine stream channel count */ + uint32_t channels = pdesc->default_channels; + if (!channels && cdesc) + channels = cdesc->caps.max_channels; + if (!channels) + channels = 2; + + /* Determine stream PCM frame format */ + enum sof_ipc_frame fmt = (cdesc && cdesc->caps.default_fmt) ? + cdesc->caps.default_fmt : host_or_sched->ipc_config.frame_fmt; + if (!fmt) + fmt = SOF_IPC_FRAME_S16_LE; + + /* Calculate container bytes, valid bytes, and period frame size */ + uint32_t cont_bytes = get_sample_bytes(fmt); + uint32_t valid_bytes = get_sample_bitdepth(fmt) / 8; + uint32_t period_us = pdesc->period ? pdesc->period : 1000; + uint32_t frames = pdesc->frames_per_sched ? pdesc->frames_per_sched : + ((rate * period_us) / 1000000); + if (frames == 0) + frames = 48; + + /* Populate PCM params structure for the pipeline */ + struct sof_ipc_pcm_params prms; + memset(&prms, 0, sizeof(prms)); + prms.params.rate = rate; + prms.params.channels = channels; + prms.params.frame_fmt = fmt; + prms.params.sample_container_bytes = cont_bytes; + prms.params.sample_valid_bytes = valid_bytes; + prms.params.buffer_fmt = SOF_IPC_BUFFER_INTERLEAVED; + prms.params.host_period_bytes = frames * channels * cont_bytes; + prms.comp_id = dev_comp_id(host_or_sched); + prms.params.direction = pdesc->direction; + for (uint32_t c = 0; c < SOF_IPC_MAX_CHANNELS; c++) { + if (c == 0) + prms.params.chmap[c] = (channels == 1) ? SOF_CHMAP_MONO : SOF_CHMAP_FL; + else if (c == 1 && channels > 1) + prms.params.chmap[c] = SOF_CHMAP_FR; + else if (c < channels) + prms.params.chmap[c] = c + 1; + else + prms.params.chmap[c] = SOF_CHMAP_NA; + } + + /* Propagate params and prepare throughout the pipeline walk */ + int ret_prms = pipeline_params(pipe, host_or_sched, &prms); + int ret_prep = pipeline_prepare(pipe, host_or_sched); + LOG_INF("Pipeline %u params (rate %u, ch %u, fmt %u) ret=%d, prepare ret=%d", + pdesc->pipeline_id, rate, channels, fmt, ret_prms, ret_prep); + } + } + + return 0; +} + +/** + * \brief Stage 6: Explicitly configure and prepare all components. + * + * Ensure every instantiated component transitions to COMP_STATE_PREPARE, + * even if not reached by the initial pipeline walk. This guarantees processing + * modules (e.g. Volume, EQ, DRC, Selector) are fully primed and ready for streaming. + * + * \param[in] topo Topology descriptor pointer. + * \return 0 on success, negative errno on failure. + */ +static int sof_static_topology_prepare_comps(const struct sof_static_topology *topo) +{ + for (size_t i = 0; i < topo->num_comps; i++) { + struct comp_dev *dev = sof_static_comp_get(topo->comps[i].id); + if (!dev) + continue; + + /* Only prepare components currently in READY state */ + if (dev->state == COMP_STATE_READY) { + const struct sof_static_comp *cdesc = &topo->comps[i]; + const struct sof_static_pipeline_desc *pdesc = NULL; + for (size_t p = 0; p < topo->num_pipelines; p++) { + if (topo->pipelines[p].pipeline_id == cdesc->pipeline_id) { + pdesc = &topo->pipelines[p]; + break; + } + } + + /* Determine component-specific sample rate */ + uint32_t rate = cdesc->caps.default_rate; + if (!rate && pdesc) + rate = pdesc->default_rate; + if (!rate) + rate = s_sample_rate; + + /* Determine channel count */ + uint32_t channels = cdesc->caps.max_channels; + if (!channels && pdesc) + channels = pdesc->default_channels; + if (!channels) + channels = 2; + + /* Determine frame format */ + enum sof_ipc_frame fmt = cdesc->caps.default_fmt ? cdesc->caps.default_fmt : + dev->ipc_config.frame_fmt; + if (!fmt) + fmt = SOF_IPC_FRAME_S16_LE; + + /* Calculate container size, valid bits, and frames */ + uint32_t cont_bytes = get_sample_bytes(fmt); + uint32_t valid_bytes = get_sample_bitdepth(fmt) / 8; + uint32_t period_us = (pdesc && pdesc->period) ? pdesc->period : 1000; + uint32_t frames = (pdesc && pdesc->frames_per_sched) ? pdesc->frames_per_sched : + ((rate * period_us) / 1000000); + if (frames == 0) + frames = 48; + + /* Populate component PCM parameters */ + struct sof_ipc_pcm_params prms; + memset(&prms, 0, sizeof(prms)); + prms.params.rate = rate; + prms.params.channels = channels; + prms.params.frame_fmt = fmt; + prms.params.sample_container_bytes = cont_bytes; + prms.params.sample_valid_bytes = valid_bytes; + prms.params.buffer_fmt = SOF_IPC_BUFFER_INTERLEAVED; + prms.params.host_period_bytes = frames * channels * cont_bytes; + prms.comp_id = dev_comp_id(dev); + prms.params.direction = dev->direction; + for (uint32_t c = 0; c < SOF_IPC_MAX_CHANNELS; c++) { + if (c == 0) + prms.params.chmap[c] = (channels == 1) ? SOF_CHMAP_MONO : SOF_CHMAP_FL; + else if (c == 1 && channels > 1) + prms.params.chmap[c] = SOF_CHMAP_FR; + else if (c < channels) + prms.params.chmap[c] = c + 1; + else + prms.params.chmap[c] = SOF_CHMAP_NA; + } + + /* Call component params and prepare callbacks */ + int ret_prms = comp_params(dev, &prms.params); + int ret_prep = comp_prepare(dev); + LOG_INF("Component %u ('%s') explicit prepare (rate %u, ch %u, fmt %u): prms=%d prep=%d state=%d", + dev_comp_id(dev), cdesc->name, rate, channels, fmt, ret_prms, ret_prep, dev->state); + } + } + + return 0; +} + +/** + * \brief Stage 7: Initialize kcontrols. + * + * Set up all declared volume, mute, enum, and binary kcontrols with their + * default boot values so processing modules start in their expected state. + * + * \param[in] topo Topology descriptor pointer. + * \return 0 on success. + */ +static int sof_static_topology_init_controls(const struct sof_static_topology *topo) +{ + for (size_t i = 0; i < topo->num_controls && i < MAX_STATIC_CONTROLS; i++) { + const struct sof_static_kcontrol *ctl = &topo->controls[i]; + s_control_vals[i] = ctl->def; + sof_static_kcontrol_set(ctl->id, ctl->def); + LOG_INF("Kcontrol [%u] '%s' (comp %u, type %d, def %d)", + ctl->id, ctl->name, ctl->target_comp_id, ctl->type, ctl->def); + } + + return 0; +} + +/** + * \brief Load and instantiate a declarative static audio topology. + * + * Executes the complete multi-stage construction pipeline: + * Stage 0: Resolve primary stream sample rate + * Stage 1: Allocate top-level pipelines + * Stage 2: Instantiate components (modules, DAIs, USB/host) + * Stage 3: Allocate intermediate audio buffers + * Stage 4: Connect pipeline graph routes + * Stage 5: Bind pipeline endpoints and propagate stream parameters + * Stage 6: Explicitly configure and prepare all components + * Stage 7: Initialize kcontrols with default boot values + * + * \param[in] topo Pointer to declarative static topology descriptor. + * \return 0 on success, negative errno on failure. + */ +int sof_static_topology_init(const struct sof_static_topology *topo) +{ + int ret; + + /* Validate input topology pointer */ + if (!topo) { + LOG_ERR("Invalid topology descriptor"); + return -EINVAL; + } + + LOG_INF("=== Loading Static Audio Topology: '%s' ===", topo->name ? topo->name : "Unnamed"); + s_active_topo = topo; + + /* Stage 0: Dynamically resolve stream sample rate */ + sof_static_topology_resolve_rate(topo); + + /* Stage 1: Allocate top-level pipelines */ + ret = sof_static_topology_init_pipelines(topo); + if (ret < 0) + return ret; + + /* Stage 2: Instantiate components */ + ret = sof_static_topology_init_comps(topo); + if (ret < 0) + return ret; + + /* Stage 3: Allocate intermediate audio buffers */ + ret = sof_static_topology_init_buffers(topo); + if (ret < 0) + return ret; + + /* Stage 4: Connect pipeline graph routes */ + ret = sof_static_topology_init_routes(topo); + if (ret < 0) + return ret; + + /* Stage 5: Bind pipeline endpoints and propagate stream parameters */ + ret = sof_static_topology_init_pipeline_endpoints(topo); + if (ret < 0) + return ret; + + /* Stage 6: Explicitly configure and prepare all components */ + ret = sof_static_topology_prepare_comps(topo); + if (ret < 0) + return ret; + + /* Stage 7: Initialize kcontrols with default values */ + ret = sof_static_topology_init_controls(topo); + if (ret < 0) + return ret; + + LOG_INF("Static audio topology initialized successfully (%zu pipelines, %zu comps, %zu buffers, %zu controls)", + topo->num_pipelines, topo->num_comps, topo->num_buffers, topo->num_controls); + return 0; +} + +/** + * \brief Retrieve the currently active static topology descriptor. + * \return Pointer to active struct sof_static_topology, or NULL if uninitialized. + */ +const struct sof_static_topology *sof_static_topology_get(void) +{ + return s_active_topo; +} + +/** + * \brief Retrieve a pipeline instance pointer by its unique pipeline ID. + * \param[in] pipeline_id Unique pipeline ID to search for. + * \return Pointer to struct pipeline, or NULL if not found. + */ +struct pipeline *sof_static_pipeline_get(uint32_t pipeline_id) +{ + /* Linear scan of statically tracked pipeline IDs */ + for (size_t i = 0; i < s_num_pipelines; i++) { + if (s_pipeline_ids[i] == pipeline_id) + return s_pipelines[i]; + } + + LOG_ERR("sof_static_pipeline_get: pipeline %u not found", pipeline_id); + return NULL; +} + +/** + * \brief Retrieve a component device pointer by its unique component ID. + * \param[in] comp_id Unique component ID to search for. + * \return Pointer to struct comp_dev, or NULL if not found. + */ +struct comp_dev *sof_static_comp_get(uint32_t comp_id) +{ + /* Linear scan of statically tracked component IDs */ + for (size_t i = 0; i < s_num_comps; i++) { + if (s_comp_ids[i] == comp_id) + return s_comps[i]; + } + + LOG_ERR("sof_static_comp_get: comp %u not found", comp_id); + return NULL; +} + +/** + * \brief Retrieve an intermediate audio buffer pointer by its unique buffer ID. + * \param[in] buffer_id Unique buffer ID to search for. + * \return Pointer to struct comp_buffer, or NULL if not found. + */ +struct comp_buffer *sof_static_buffer_get(uint32_t buffer_id) +{ + /* Linear scan of statically tracked buffer IDs */ + for (size_t i = 0; i < s_num_buffers; i++) { + if (s_buffer_ids[i] == buffer_id) + return s_buffers[i]; + } + + LOG_ERR("sof_static_buffer_get: buffer %u not found", buffer_id); + return NULL; +} + +/** + * \brief Set kcontrol value by control ID. + * + * Dispatches control updates to the custom callback handler if registered, + * or routes volume, switch, or enum controls to target components via their + * registered static module operations (struct sof_static_module_ops). + * + * \param[in] ctrl_id Unique control identifier. + * \param[in] val Value to apply. + * \return 0 on success, negative errno on failure. + */ +int sof_static_kcontrol_set(uint32_t ctrl_id, int32_t val) +{ + if (!s_active_topo) + return -ENODEV; + + /* Look up control descriptor by unique ID */ + const struct sof_static_kcontrol *ctl = NULL; + size_t ctl_idx = 0; + for (size_t i = 0; i < s_active_topo->num_controls; i++) { + if (s_active_topo->controls[i].id == ctrl_id) { + ctl = &s_active_topo->controls[i]; + ctl_idx = i; + break; + } + } + if (!ctl) { + LOG_ERR("sof_static_kcontrol_set: control ID %u not found", ctrl_id); + return -ENOENT; + } + + /* + * Custom kcontrol callback handler: + * Allows board- or platform-specific controls (such as hardware clock + * switching, route multiplexers, or DMIC injection) to intercept control events. + */ + if (s_active_topo->custom_control_handler) { + int ret = s_active_topo->custom_control_handler(ctl, val, s_active_topo->custom_control_data); + if (ret >= 0 || ctl->target_comp_id == 0) { + s_control_vals[ctl_idx] = val; + LOG_INF("Kcontrol [%u] '%s' set to %d via custom handler", ctl->id, ctl->name, val); + return ret; + } + } + + /* Virtual / platform controls with no target component */ + if (ctl->target_comp_id == 0) { + s_control_vals[ctl_idx] = val; + LOG_INF("Kcontrol [%u] '%s' stored val=%d", ctl->id, ctl->name, val); + return 0; + } + + /* Look up target component device */ + struct comp_dev *dev = sof_static_comp_get(ctl->target_comp_id); + if (!dev) + return -ENODEV; + + /* Look up component's registered static module operations */ + const struct sof_static_module_ops *ops = dev->drv ? sof_static_find_module_ops(dev->drv->uid) : NULL; + + /* Dispatch control value to target component using registered operations */ + if (ops) { + switch (ctl->type) { + case SOF_STATIC_CTRL_VOLUME: + /* Linear volume fader control */ + if (ops->apply_volume) + ops->apply_volume(dev, ctl->channels, val); + break; + + case SOF_STATIC_CTRL_SWITCH: + /* Boolean mute or module bypass switch */ + if (ops->apply_switch) + ops->apply_switch(dev, ctl->channels, val); + break; + + case SOF_STATIC_CTRL_ENUM: + /* Enumerated route or channel selector */ + if (ops->apply_enum) + ops->apply_enum(dev, 0, val); + break; + + default: + LOG_ERR("sof_static_kcontrol_set: invalid control type %d for ctl %u", + ctl->type, ctl->id); + break; + } + } + + /* Cache updated control value and log confirmation */ + s_control_vals[ctl_idx] = val; + LOG_INF("Kcontrol [%u] '%s' set to %d", ctl->id, ctl->name, val); + return 0; +} + +/** + * \brief Retrieve cached kcontrol value by control ID. + * \param[in] ctrl_id Unique control identifier. + * \param[out] val Pointer to store retrieved value. + * \return 0 on success, negative errno on failure. + */ +int sof_static_kcontrol_get(uint32_t ctrl_id, int32_t *val) +{ + if (!s_active_topo || !val) + return -EINVAL; + + /* Search control descriptors for matching ID and return cached value */ + for (size_t i = 0; i < s_active_topo->num_controls; i++) { + if (s_active_topo->controls[i].id == ctrl_id) { + *val = s_control_vals[i]; + return 0; + } + } + + LOG_ERR("sof_static_kcontrol_get: control ID %u not found", ctrl_id); + return -ENOENT; +} + +/** + * \brief Find kcontrol ID by its human-readable name. + * \param[in] name Control name string to search for. + * \return Control ID >= 0 if found, -ENOENT if not found. + */ +int sof_static_kcontrol_find_by_name(const char *name) +{ + if (!s_active_topo || !name) + return -EINVAL; + + /* Compare name string against declared controls in active topology */ + for (size_t i = 0; i < s_active_topo->num_controls; i++) { + if (s_active_topo->controls[i].name && + strcmp(s_active_topo->controls[i].name, name) == 0) { + return (int)s_active_topo->controls[i].id; + } + } + + LOG_ERR("sof_static_kcontrol_find_by_name: control '%s' not found", name); + return -ENOENT; +} + +/** + * \brief Resolve the primary trigger component driving a pipeline. + * + * For playback pipelines, the trigger component is typically sched_comp or source_comp. + * For capture pipelines, where data flows into the DSP from an external DAI or source, + * the trigger component must be the consumer/sink endpoint to pull data through the graph. + * + * \param[in] pipe Target pipeline pointer. + * \return Pointer to resolved trigger component device. + */ +static struct comp_dev *sof_static_pipeline_get_trigger_dev(struct pipeline *pipe) +{ + struct comp_dev *dev = pipe->sched_comp; + + /* For capture pipelines, ensure trigger starts from the sink component */ + if (!dev || (pipe->source_comp && pipe->source_comp->direction == SOF_IPC_STREAM_CAPTURE && + dev == pipe->source_comp)) { + if (pipe->source_comp && pipe->source_comp->direction == SOF_IPC_STREAM_CAPTURE) + dev = pipe->sink_comp; + else + dev = pipe->source_comp; + } + return dev; +} + +/** + * \brief Start a static audio pipeline by its pipeline ID. + * + * Prepares the pipeline and constituent components if needed, propagates + * start triggers, transitions state to COMP_STATE_ACTIVE, and activates + * scheduling copy tasks. + * + * \param[in] pipeline_id Target pipeline ID. + * \return 0 on success, negative errno on failure. + */ +int sof_static_pipeline_start(uint32_t pipeline_id) +{ + /* Look up target pipeline */ + struct pipeline *pipe = sof_static_pipeline_get(pipeline_id); + if (!pipe) + return -ENOENT; + + /* Resolve trigger component */ + struct comp_dev *dev = sof_static_pipeline_get_trigger_dev(pipe); + if (!dev) + return -ENODEV; + + /* If not already active, prepare and trigger components */ + if (pipe->status != COMP_STATE_ACTIVE) { + /* + * Ensure pipeline is prepared before starting. + * If prepare fails, abort immediately: triggering a pipeline whose + * circular buffers and DMA channels are uninitialized causes DSP faults. + */ + if (pipe->status == COMP_STATE_READY || + pipe->status == COMP_STATE_INIT || + pipe->status == COMP_STATE_PAUSED) { + int ret_prep = pipeline_prepare(pipe, dev); + if (ret_prep < 0) { + LOG_ERR("Pipeline %u prepare failed: %d", pipeline_id, ret_prep); + return ret_prep; + } + } + + /* Propagate PRE_START trigger across the pipeline graph */ + int ret = pipeline_trigger_run(pipe, dev, COMP_TRIGGER_PRE_START); + if (ret < 0) { + LOG_ERR("Pipeline %u pre-start trigger failed: %d", pipeline_id, ret); + return ret; + } + + /* Propagate START trigger across the pipeline graph */ + ret = pipeline_trigger_run(pipe, dev, COMP_TRIGGER_START); + if (ret < 0) { + LOG_ERR("Pipeline %u start trigger failed: %d", pipeline_id, ret); + return ret; + } + + /* Explicitly notify all constituent components and mark state ACTIVE */ + if (s_active_topo) { + for (size_t j = 0; j < s_active_topo->num_comps; j++) { + if (s_active_topo->comps[j].pipeline_id == pipeline_id) { + struct comp_dev *c = sof_static_comp_get(s_active_topo->comps[j].id); + if (c) { + comp_trigger(c, COMP_TRIGGER_PRE_START); + comp_trigger(c, COMP_TRIGGER_START); + c->state = COMP_STATE_ACTIVE; + } + } + } + } + + pipe->status = COMP_STATE_ACTIVE; + } + + /* Schedule periodic copy task if not already running */ + if (pipe->pipe_task && !task_is_active(pipe->pipe_task)) + pipeline_schedule_copy(pipe, 0); + + LOG_INF("Pipeline %u STARTED", pipeline_id); + return 0; +} + +/** + * \brief Stop a static audio pipeline by its pipeline ID. + * + * Propagates stop triggers, cancels scheduling copy tasks, and transitions + * pipeline and component states to COMP_STATE_PAUSED. + * + * \param[in] pipeline_id Target pipeline ID. + * \return 0 on success, negative errno on failure. + */ +int sof_static_pipeline_stop(uint32_t pipeline_id) +{ + /* Look up target pipeline */ + struct pipeline *pipe = sof_static_pipeline_get(pipeline_id); + if (!pipe) + return -ENOENT; + + /* Resolve trigger component */ + struct comp_dev *dev = sof_static_pipeline_get_trigger_dev(pipe); + if (!dev) + return -ENODEV; + + /* Propagate STOP trigger if pipeline is active or paused */ + if (pipe->status == COMP_STATE_ACTIVE || pipe->status == COMP_STATE_PAUSED) { + int ret = pipeline_trigger_run(pipe, dev, COMP_TRIGGER_STOP); + if (ret < 0) { + LOG_ERR("Pipeline %u stop trigger failed: %d", pipeline_id, ret); + return ret; + } + + /* Explicitly notify all constituent components and mark state PAUSED */ + if (s_active_topo) { + for (size_t j = 0; j < s_active_topo->num_comps; j++) { + if (s_active_topo->comps[j].pipeline_id == pipeline_id) { + struct comp_dev *c = sof_static_comp_get(s_active_topo->comps[j].id); + if (c) { + comp_trigger(c, COMP_TRIGGER_STOP); + c->state = COMP_STATE_PAUSED; + } + } + } + } + + pipe->status = COMP_STATE_PAUSED; + } + + /* Cancel active periodic copy task */ + if (pipe->pipe_task && task_is_active(pipe->pipe_task)) + schedule_task_cancel(pipe->pipe_task); + + LOG_INF("Pipeline %u STOPPED", pipeline_id); + return 0; +} + +/** + * \brief Directly trigger a static pipeline start or stop by pipeline ID. + * + * Dispatches cleanly to sof_static_pipeline_start() or sof_static_pipeline_stop(). + * + * \param[in] pipeline_id Target pipeline ID. + * \param[in] start True to start pipeline, false to stop. + * \return 0 on success, negative errno on failure. + */ +int sof_static_pipeline_trigger(uint32_t pipeline_id, bool start) +{ + if (start) + return sof_static_pipeline_start(pipeline_id); + + return sof_static_pipeline_stop(pipeline_id); +} + +/** + * \brief Override the primary stream sample rate across active static pipelines. + * \param[in] rate Target sample rate in Hz (e.g. 48000). + * \return 0 on success. + */ +int sof_static_set_sample_rate(uint32_t rate) +{ + s_sample_rate = rate; + return 0; +} + +/** + * \brief Retrieve the current primary stream sample rate. + * \return Current sample rate in Hz. + */ +uint32_t sof_static_get_sample_rate(void) +{ + /* + * Return the active primary sample rate. If topology init has run, + * this is derived from the declarative topology or runtime override; + * if queried before init, fall back to 48 kHz. + */ + return s_sample_rate ? s_sample_rate : 48000; +} diff --git a/src/audio/pipeline/static_pipeline_modules.c b/src/audio/pipeline/static_pipeline_modules.c new file mode 100644 index 000000000000..ad723c29592f --- /dev/null +++ b/src/audio/pipeline/static_pipeline_modules.c @@ -0,0 +1,240 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2026 Sound Open Firmware (SOF) Project + */ + +/** + * \file audio/pipeline/static_pipeline_modules.c + * \brief Generic Static Module Operations Registry & Infrastructure + * \author Liam Girdwood + * + * Implements generic module operations registration, lookup, and default + * component initialization helpers for declarative static pipelines. + * This file contains purely generic infrastructure with zero per-module logic. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "static_pipeline_modules.h" + +LOG_MODULE_REGISTER(static_pipeline_modules, CONFIG_SOF_LOG_LEVEL); + +/* ========================================================================= + * Static Module Operations Registry + * ========================================================================= */ + +static struct list_item s_module_ops_list = LIST_INIT(s_module_ops_list); + +/** + * \brief Register static module operations for an audio component. + * \param[in,out] ops Pointer to module operations structure. + * \return 0 on success, negative error code on failure. + */ +int sof_static_register_module_ops(struct sof_static_module_ops *ops) +{ + if (!ops) { + LOG_ERR("sof_static_register_module_ops: NULL ops pointer"); + return -EINVAL; + } + if (!ops->uuid) { + LOG_ERR("sof_static_register_module_ops: NULL ops UUID"); + return -EINVAL; + } + + list_item_append(&ops->list, &s_module_ops_list); + return 0; +} + +/** + * \brief Find registered static module operations by component UUID. + * \param[in] uuid UUID of target component. + * \return Pointer to registered ops, or NULL if not found or on error. + */ +const struct sof_static_module_ops *sof_static_find_module_ops(const struct sof_uuid *uuid) +{ + if (!uuid) { + LOG_ERR("sof_static_find_module_ops: NULL UUID pointer"); + return NULL; + } + + struct list_item *item; + list_for_item(item, &s_module_ops_list) { + struct sof_static_module_ops *ops = + container_of(item, struct sof_static_module_ops, list); + if (!memcmp(ops->uuid, uuid, UUID_SIZE)) + return ops; + } + + LOG_ERR("sof_static_find_module_ops: no ops registered for requested UUID"); + return NULL; +} + +/* ========================================================================= + * Base IPC4 Configuration & Default Component Creation + * ========================================================================= */ + +#if CONFIG_IPC4_BASE_CONFIG || CONFIG_IPC_MAJOR_4 + +/* Channel map slot macro: channel index 'ch' placed at slot index 'slot' */ +#define SOF_CH_MAP_SLOT(slot, ch) (((uint32_t)((ch) & 0xf)) << ((slot) * 4)) + +#define SOF_CH_MAP_1CH(c0) \ + (0xfffffff0 | SOF_CH_MAP_SLOT(0, (c0))) + +#define SOF_CH_MAP_2CH(c0, c1) \ + (0xffffff00 | SOF_CH_MAP_SLOT(0, (c0)) | SOF_CH_MAP_SLOT(1, (c1))) + +#define SOF_CH_MAP_3CH(c0, c1, c2) \ + (0xfffff000 | SOF_CH_MAP_SLOT(0, (c0)) | SOF_CH_MAP_SLOT(1, (c1)) | \ + SOF_CH_MAP_SLOT(2, (c2))) + +#define SOF_CH_MAP_4CH(c0, c1, c2, c3) \ + (0xffff0000 | SOF_CH_MAP_SLOT(0, (c0)) | SOF_CH_MAP_SLOT(1, (c1)) | \ + SOF_CH_MAP_SLOT(2, (c2)) | SOF_CH_MAP_SLOT(3, (c3))) + +#define SOF_CH_MAP_6CH(c0, c1, c2, c3, c4, c5) \ + (0xff000000 | SOF_CH_MAP_SLOT(0, (c0)) | SOF_CH_MAP_SLOT(1, (c1)) | \ + SOF_CH_MAP_SLOT(2, (c2)) | SOF_CH_MAP_SLOT(3, (c3)) | \ + SOF_CH_MAP_SLOT(4, (c4)) | SOF_CH_MAP_SLOT(5, (c5))) + +#define SOF_CH_MAP_8CH(c0, c1, c2, c3, c4, c5, c6, c7) \ + (SOF_CH_MAP_SLOT(0, (c0)) | SOF_CH_MAP_SLOT(1, (c1)) | \ + SOF_CH_MAP_SLOT(2, (c2)) | SOF_CH_MAP_SLOT(3, (c3)) | \ + SOF_CH_MAP_SLOT(4, (c4)) | SOF_CH_MAP_SLOT(5, (c5)) | \ + SOF_CH_MAP_SLOT(6, (c6)) | SOF_CH_MAP_SLOT(7, (c7))) + +void sof_static_init_base_cfg(struct ipc4_base_module_cfg *base_cfg, + const struct sof_static_comp *cdesc, + uint32_t period_us) +{ + if (!base_cfg || !cdesc) { + LOG_ERR("sof_static_init_base_cfg: NULL parameter"); + return; + } + + memset(base_cfg, 0, sizeof(*base_cfg)); + + uint32_t rate = cdesc->caps.default_rate ? cdesc->caps.default_rate : 48000; + uint16_t channels = cdesc->caps.max_channels ? cdesc->caps.max_channels : 2; + enum sof_ipc_frame fmt = cdesc->caps.default_fmt ? cdesc->caps.default_fmt : SOF_IPC_FRAME_S16_LE; + + uint32_t cont_bytes = get_sample_bytes(fmt); + uint32_t valid_bits = get_sample_bitdepth(fmt); + uint32_t frames = (rate * (period_us ? period_us : 1000)) / 1000000; + + /* + * Fallback assumption: if integer division resulted in zero frames + * (e.g. invalid rate or ultra-low scheduling period), default to 48 frames, + * representing a standard 1 ms period at 48 kHz (48000 * 1000 / 1000000 = 48). + * Non-zero buffer size is required by IPC4 module infrastructure. + */ + if (frames == 0) + frames = 48; + + base_cfg->cpc = 0; + base_cfg->is_pages = 1; + base_cfg->ibs = frames * channels * cont_bytes; + + /* If sink_rate is specified (e.g. for SRC / ASRC), compute OBS from sink rate */ + if (cdesc->caps.sink_rate) { + uint32_t out_frames = (cdesc->caps.sink_rate * (period_us ? period_us : 1000)) / 1000000; + /* Fallback assumption: same 48-frame minimum for sink buffer */ + if (out_frames == 0) + out_frames = 48; + base_cfg->obs = out_frames * channels * cont_bytes; + } else { + base_cfg->obs = base_cfg->ibs; + } + + base_cfg->audio_fmt.sampling_frequency = rate; + base_cfg->audio_fmt.depth = (cont_bytes * 8); + base_cfg->audio_fmt.valid_bit_depth = valid_bits; + base_cfg->audio_fmt.channels_count = channels; + base_cfg->audio_fmt.interleaving_style = IPC4_CHANNELS_INTERLEAVED; + + if (fmt == SOF_IPC_FRAME_FLOAT) + base_cfg->audio_fmt.s_type = IPC4_TYPE_FLOAT; + else if (fmt == SOF_IPC_FRAME_A_LAW) + base_cfg->audio_fmt.s_type = IPC4_TYPE_A_LAW; + else if (fmt == SOF_IPC_FRAME_MU_LAW) + base_cfg->audio_fmt.s_type = IPC4_TYPE_MU_LAW; + else + base_cfg->audio_fmt.s_type = IPC4_TYPE_SIGNED_INTEGER; + + switch (channels) { + case 1: + base_cfg->audio_fmt.ch_cfg = IPC4_CHANNEL_CONFIG_MONO; + base_cfg->audio_fmt.ch_map = SOF_CH_MAP_1CH(CHANNEL_CENTER); + break; + case 2: + base_cfg->audio_fmt.ch_cfg = IPC4_CHANNEL_CONFIG_STEREO; + base_cfg->audio_fmt.ch_map = SOF_CH_MAP_2CH(CHANNEL_LEFT, CHANNEL_RIGHT); + break; + case 3: + base_cfg->audio_fmt.ch_cfg = IPC4_CHANNEL_CONFIG_3_POINT_0; + base_cfg->audio_fmt.ch_map = SOF_CH_MAP_3CH(CHANNEL_LEFT, CHANNEL_CENTER, + CHANNEL_RIGHT); + break; + case 4: + base_cfg->audio_fmt.ch_cfg = IPC4_CHANNEL_CONFIG_QUATRO; + base_cfg->audio_fmt.ch_map = SOF_CH_MAP_4CH(CHANNEL_LEFT, CHANNEL_RIGHT, + CHANNEL_LEFT_SURROUND, + CHANNEL_RIGHT_SURROUND); + break; + case 6: + base_cfg->audio_fmt.ch_cfg = IPC4_CHANNEL_CONFIG_5_POINT_1; + base_cfg->audio_fmt.ch_map = SOF_CH_MAP_6CH(CHANNEL_LEFT, CHANNEL_CENTER, + CHANNEL_RIGHT, CHANNEL_LEFT_SURROUND, + CHANNEL_RIGHT_SURROUND, CHANNEL_LFE); + break; + case 8: + base_cfg->audio_fmt.ch_cfg = IPC4_CHANNEL_CONFIG_7_POINT_1; + base_cfg->audio_fmt.ch_map = SOF_CH_MAP_8CH(CHANNEL_LEFT, CHANNEL_CENTER, + CHANNEL_RIGHT, CHANNEL_LEFT_SURROUND, + CHANNEL_RIGHT_SURROUND, CHANNEL_LFE, + CHANNEL_LEFT_SIDE, CHANNEL_RIGHT_SIDE); + break; + default: + base_cfg->audio_fmt.ch_cfg = IPC4_CHANNEL_CONFIG_STEREO; + base_cfg->audio_fmt.ch_map = SOF_CH_MAP_2CH(CHANNEL_LEFT, CHANNEL_RIGHT); + break; + } +} +#endif + +struct comp_dev *sof_static_module_create_default(const struct comp_driver *drv, + struct comp_ipc_config *cfg, + const struct sof_static_comp *cdesc, + uint32_t period_us) +{ + if (!drv || !cfg || !cdesc) { + LOG_ERR("sof_static_module_create_default: NULL parameter"); + return NULL; + } + +#if CONFIG_IPC4_BASE_CONFIG || CONFIG_IPC_MAJOR_4 + struct ipc4_base_module_cfg base_cfg; + sof_static_init_base_cfg(&base_cfg, cdesc, period_us); + struct ipc_config_process base_proc_spec = { + .size = sizeof(base_cfg), + .data = (const uint8_t *)&base_cfg, + }; + return drv->ops.create(drv, cfg, &base_proc_spec); +#else + static const uint8_t dummy_buf[16] = {0}; + struct ipc_config_process empty_proc_spec = { + .size = 0, + .data = dummy_buf, + }; + return drv->ops.create(drv, cfg, &empty_proc_spec); +#endif +} diff --git a/src/audio/pipeline/static_pipeline_modules.h b/src/audio/pipeline/static_pipeline_modules.h new file mode 100644 index 000000000000..4ce8195722a5 --- /dev/null +++ b/src/audio/pipeline/static_pipeline_modules.h @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2026 Sound Open Firmware (SOF) Project + */ + +/** + * \file audio/pipeline/static_pipeline_modules.h + * \brief Generic internal interfaces for static pipeline module operations. + * \author Liam Girdwood + * + * This private header declares internal helpers shared between the static + * pipeline loader and module adapter implementations. It contains no + * component-specific logic. + */ + +#ifndef __SOF_AUDIO_PIPELINE_STATIC_PIPELINE_MODULES_H__ +#define __SOF_AUDIO_PIPELINE_STATIC_PIPELINE_MODULES_H__ + +#include +#include + +/** + * \brief Synthesize standard IPC4 base module configuration structure. + * \param[out] base_cfg Target base configuration to populate. + * \param[in] cdesc Static component descriptor with format and rate capabilities. + * \param[in] period_us Pipeline scheduling period in microseconds. + */ +void sof_static_init_base_cfg(struct ipc4_base_module_cfg *base_cfg, + const struct sof_static_comp *cdesc, + uint32_t period_us); + +/** + * \brief Default fallback creation for standard IPC4 module adapters. + * \param[in] drv SOF component driver. + * \param[in] cfg IPC component configuration. + * \param[in] cdesc Static component descriptor. + * \param[in] period_us Owning pipeline scheduling period in microseconds. + * \return Created comp_dev pointer, or NULL on failure. + */ +struct comp_dev *sof_static_module_create_default(const struct comp_driver *drv, + struct comp_ipc_config *cfg, + const struct sof_static_comp *cdesc, + uint32_t period_us); + +#endif /* __SOF_AUDIO_PIPELINE_STATIC_PIPELINE_MODULES_H__ */ diff --git a/src/audio/pipeline/static_pipeline_uac2.c b/src/audio/pipeline/static_pipeline_uac2.c new file mode 100644 index 000000000000..193e1f29e862 --- /dev/null +++ b/src/audio/pipeline/static_pipeline_uac2.c @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 Sound Open Firmware (SOF) Project +// +// Author: Liam Girdwood + +/** + * \file audio/pipeline/static_pipeline_uac2.c + * \brief USB Audio Class 2.0 (UAC2) Bridge & Control Operations for Static Pipelines + * \author Liam Girdwood + * + * Provides translation between USB Audio Class 2.0 (UAC2) host requests (volume, + * mute switch, streaming terminal triggers) and Sound Open Firmware static + * topology components and kcontrols. + */ + +#include +#include +#include + +LOG_MODULE_REGISTER(static_pipeline_uac2, CONFIG_SOF_LOG_LEVEL); + +/* Standard 0 dB gain constant for SOF volume representation (Q1.16 format) */ +#define SOF_VOL_ZERO_DB 65536 + +/** + * \brief Convert USB Audio Class 2.0 8.8 fixed-point dB volume to linear SOF volume. + * + * UAC2 expresses volume in 1/256 dB steps (e.g. 0x0000 = 0 dB, -90 dB = 0xa600). + * This helper translates UAC2 dB volume into SOF 16-bit linear volume using + * an integer 6 dB per bit shift approximation: + * - >= 0 dB maps to SOF_VOL_ZERO_DB (unity gain) + * - <= -90 dB maps to 0 (digital silence) + * - Values between -90 dB and 0 dB are scaled proportionally. + * + * \param[in] volume USB Audio Class 2.0 8.8 fixed-point dB volume value. + * \return 16-bit linear volume value (0 to SOF_VOL_ZERO_DB). + */ +static int32_t uac2_to_sof_volume(int16_t volume) +{ + /* Treat volume below -90 dB as complete digital silence */ + if (volume <= -90 * 256) + return 0; + + /* Clip volume at or above 0 dB to unity gain */ + if (volume >= 0) + return SOF_VOL_ZERO_DB; + + /* Convert negative dB (scaled by 256) to tenths of dB: 1 dB = ~6 dB per bit */ + int32_t db_x10 = (int32_t)(-volume) * 10 / 256; + int shift = db_x10 / 60; + if (shift >= 31) + return 0; + + /* Linear interpolation between 6 dB bit shifts */ + int rem = db_x10 % 60; + uint64_t v = (uint64_t)SOF_VOL_ZERO_DB >> shift; + v = (v * (60 - rem)) / 60; + return (int32_t)v; +} + +/** + * \brief Dispatch USB Audio Class 2.0 Feature Unit control requests to matching kcontrols. + * + * Maps incoming USB Audio Class 2 (UAC2) volume or mute requests to the corresponding + * static kcontrol bound to that UAC2 entity ID. + * + * \param[in] entity_id UAC2 Feature Unit Entity ID. + * \param[in] channel Audio channel index (0 = master/all). + * \param[in] val Value from USB request (8.8 fixed-point dB volume or boolean mute). + * \param[in] is_volume True for volume command, false for mute switch command. + * \return 0 on success, negative errno on failure. + */ +int sof_static_kcontrol_set_by_uac2(uint8_t entity_id, uint8_t channel, int32_t val, bool is_volume) +{ + const struct sof_static_topology *topo = sof_static_topology_get(); + + if (!topo) { + LOG_ERR("sof_static_kcontrol_set_by_uac2: no active topology"); + return -ENODEV; + } + + /* Search controls in active topology for matching UAC2 entity ID */ + for (size_t i = 0; i < topo->num_controls; i++) { + const struct sof_static_kcontrol *ctl = &topo->controls[i]; + + if (ctl->uac2_entity_id == entity_id) { + if (is_volume && ctl->type == SOF_STATIC_CTRL_VOLUME) { + /* Translate UAC2 8.8 dB volume to linear SOF volume */ + int32_t sof_vol = uac2_to_sof_volume((int16_t)val); + return sof_static_kcontrol_set(ctl->id, sof_vol); + } else if (!is_volume && ctl->type == SOF_STATIC_CTRL_SWITCH) { + /* In UAC2: val=1 means MUTED, so enabled switch state is 0 */ + return sof_static_kcontrol_set(ctl->id, val ? 0 : 1); + } + } + } + + LOG_ERR("sof_static_kcontrol_set_by_uac2: no control found for UAC2 entity %u", entity_id); + return -ENOENT; +} + +/** + * \brief Query kcontrol value formatted for USB Audio Class 2.0 response. + * + * \param[in] entity_id UAC2 Feature Unit Entity ID. + * \param[in] channel Audio channel index. + * \param[out] val Pointer to store retrieved value. + * \param[in] is_volume True for volume query, false for mute query. + * \return 0 on success, negative errno on failure. + */ +int sof_static_kcontrol_get_by_uac2(uint8_t entity_id, uint8_t channel, int32_t *val, bool is_volume) +{ + const struct sof_static_topology *topo = sof_static_topology_get(); + + if (!topo || !val) { + LOG_ERR("sof_static_kcontrol_get_by_uac2: invalid topology or null val pointer"); + return -EINVAL; + } + + /* Search controls in active topology for matching UAC2 entity ID */ + for (size_t i = 0; i < topo->num_controls; i++) { + const struct sof_static_kcontrol *ctl = &topo->controls[i]; + + if (ctl->uac2_entity_id == entity_id) { + int32_t ctl_val = 0; + int ret = sof_static_kcontrol_get(ctl->id, &ctl_val); + if (ret < 0) { + LOG_ERR("sof_static_kcontrol_get_by_uac2: failed to get ctl %u", ctl->id); + return ret; + } + + if (is_volume && ctl->type == SOF_STATIC_CTRL_VOLUME) { + *val = ctl_val; + return 0; + } else if (!is_volume && ctl->type == SOF_STATIC_CTRL_SWITCH) { + /* In UAC2: mute is 1 if disabled (0), 0 if enabled (1) */ + *val = (ctl_val == 0) ? 1 : 0; + return 0; + } + } + } + + LOG_ERR("sof_static_kcontrol_get_by_uac2: no control found for UAC2 entity %u", entity_id); + return -ENOENT; +} + +/** + * \brief Trigger pipeline start or stop associated with a UAC2 terminal ID. + * + * Finds the host streaming component bound to the given USB terminal ID + * and delegates to sof_static_pipeline_start() or sof_static_pipeline_stop(). + * + * \param[in] terminal_id Bound UAC2 Terminal Entity ID. + * \param[in] start True to start pipeline, false to pause/stop. + * \return 0 on success, negative errno on failure. + */ +int sof_static_pipeline_trigger_by_uac2_term(uint8_t terminal_id, bool start) +{ + const struct sof_static_topology *topo = sof_static_topology_get(); + + if (!topo) { + LOG_ERR("sof_static_pipeline_trigger_by_uac2_term: no active topology"); + return -ENODEV; + } + + /* Find host component matching terminal_id */ + for (size_t i = 0; i < topo->num_comps; i++) { + const struct sof_static_comp *cdesc = &topo->comps[i]; + + if (cdesc->type == SOF_STATIC_COMP_HOST && cdesc->ep.usb.terminal_id == terminal_id) { + /* Delegate directly to modular start/stop routines */ + int ret = start ? sof_static_pipeline_start(cdesc->pipeline_id) : + sof_static_pipeline_stop(cdesc->pipeline_id); + + if (ret == 0) + LOG_INF("Pipeline %u %s via UAC2 terminal %u", cdesc->pipeline_id, + start ? "STARTED" : "STOPPED", terminal_id); + else + LOG_ERR("Pipeline %u %s via UAC2 terminal %u failed: %d", + cdesc->pipeline_id, start ? "start" : "stop", terminal_id, ret); + + return ret; + } + } + + LOG_ERR("sof_static_pipeline_trigger_by_uac2_term: no host component found for terminal %u", terminal_id); + return -ENOENT; +} diff --git a/src/audio/selector/selector.c b/src/audio/selector/selector.c index bc48f99b5126..89e74a2d0531 100644 --- a/src/audio/selector/selector.c +++ b/src/audio/selector/selector.c @@ -1182,3 +1182,73 @@ SOF_MODULE_INIT(selector, sys_comp_module_selector_interface_init); #endif #endif + +#if CONFIG_STATIC_PIPELINE +#include +#include + +static struct comp_dev *sel_static_create(const struct comp_driver *drv, + struct comp_ipc_config *cfg, + const struct sof_static_comp *cdesc, + uint32_t period_us) +{ + uint16_t ch = cdesc->caps.max_channels ? cdesc->caps.max_channels : 2; + struct sof_sel_config sel_cfg = { + .in_channels_count = ch, + .out_channels_count = ch, + .sel_channel = 0, + }; + struct ipc_config_process sel_spec = { + .size = sizeof(sel_cfg), + .data = (const uint8_t *)&sel_cfg, + }; + return drv->ops.create(drv, cfg, &sel_spec); +} + +static int sel_static_apply_enum(struct comp_dev *dev, uint32_t channel, int32_t val) +{ +#if CONFIG_IPC_MAJOR_3 + uint8_t cbuf[sizeof(struct sof_ipc_ctrl_data) + sizeof(struct sof_ipc_ctrl_value_chan)] = {0}; + struct sof_ipc_ctrl_data *cdata = (struct sof_ipc_ctrl_data *)cbuf; + + cdata->cmd = SOF_CTRL_CMD_ENUM; + cdata->type = SOF_CTRL_TYPE_VALUE_CHAN_SET; + cdata->num_elems = 1; + cdata->chanv[0].channel = channel; + cdata->chanv[0].value = val; + if (dev->drv && dev->drv->ops.cmd) + return dev->drv->ops.cmd(dev, COMP_CMD_SET_VALUE, cdata, sizeof(cbuf)); + return 0; +#else + struct processing_module *mod = comp_mod(dev); + + if (!mod) + return -EINVAL; + + struct comp_data *cd = module_get_private_data(mod); + + if (cd) + cd->config.sel_channel = val; + + return 0; +#endif +} + +#if CONFIG_IPC_MAJOR_3 +static struct sof_static_module_ops sel_static_ops = { + .uuid = &selector_uuid, + .create = sel_static_create, + .apply_enum = sel_static_apply_enum, +}; +DECLARE_STATIC_MODULE_OPS(selector, &sel_static_ops); +#endif + +#if CONFIG_IPC_MAJOR_4 +static struct sof_static_module_ops sel4_static_ops = { + .uuid = &selector4_uuid, + .create = sel_static_create, + .apply_enum = sel_static_apply_enum, +}; +DECLARE_STATIC_MODULE_OPS(selector4, &sel4_static_ops); +#endif +#endif /* CONFIG_STATIC_PIPELINE */ diff --git a/src/audio/src/src.c b/src/audio/src/src.c index 9870ad911ca8..32470404f52d 100644 --- a/src/audio/src/src.c +++ b/src/audio/src/src.c @@ -107,4 +107,35 @@ SOF_LLEXT_BUILDINFO; DECLARE_MODULE_ADAPTER(src_interface, SRC_UUID, src_tr); SOF_MODULE_INIT(src, sys_comp_module_src_interface_init); +#if CONFIG_STATIC_PIPELINE +#include + +#if CONFIG_IPC_MAJOR_4 +static struct comp_dev *src_static_create(const struct comp_driver *drv, + struct comp_ipc_config *cfg, + const struct sof_static_comp *cdesc, + uint32_t period_us) +{ + struct ipc4_config_src src_cfg; + + memset(&src_cfg, 0, sizeof(src_cfg)); + sof_static_init_base_cfg(&src_cfg.base, cdesc, period_us); + src_cfg.sink_rate = cdesc->caps.sink_rate ? cdesc->caps.sink_rate : cdesc->caps.default_rate; + + struct ipc_config_process spec = { + .size = sizeof(src_cfg), + .data = (const uint8_t *)&src_cfg, + }; + return drv->ops.create(drv, cfg, &spec); +} + +static struct sof_static_module_ops src_static_ops = { + .uuid = &SRC_UUID, + .create = src_static_create, +}; + +DECLARE_STATIC_MODULE_OPS(src, &src_static_ops); +#endif /* CONFIG_IPC_MAJOR_4 */ +#endif /* CONFIG_STATIC_PIPELINE */ + #endif diff --git a/src/audio/tdfb/tdfb.c b/src/audio/tdfb/tdfb.c index eed22f395632..c119a0987f09 100644 --- a/src/audio/tdfb/tdfb.c +++ b/src/audio/tdfb/tdfb.c @@ -945,4 +945,30 @@ DECLARE_TR_CTX(tdfb_tr, SOF_UUID(tdfb_uuid), LOG_LEVEL_INFO); DECLARE_MODULE_ADAPTER(tdfb_interface, tdfb_uuid, tdfb_tr); SOF_MODULE_INIT(tdfb, sys_comp_module_tdfb_interface_init); +#if CONFIG_STATIC_PIPELINE +#include + +static int tdfb_static_apply_switch(struct comp_dev *dev, uint32_t channels, int32_t val) +{ + struct processing_module *mod = comp_mod(dev); + + if (!mod) + return -EINVAL; + + struct tdfb_comp_data *cd = module_get_private_data(mod); + + if (cd) + cd->beam_on = (val != 0); + + return 0; +} + +static struct sof_static_module_ops tdfb_static_ops = { + .uuid = &tdfb_uuid, + .apply_switch = tdfb_static_apply_switch, +}; + +DECLARE_STATIC_MODULE_OPS(tdfb, &tdfb_static_ops); +#endif /* CONFIG_STATIC_PIPELINE */ + #endif diff --git a/src/audio/volume/volume.c b/src/audio/volume/volume.c index 65d4d1e674e6..1c903f5026cb 100644 --- a/src/audio/volume/volume.c +++ b/src/audio/volume/volume.c @@ -889,4 +889,103 @@ DECLARE_MODULE_ADAPTER(gain_interface, gain_uuid, gain_tr); SOF_MODULE_INIT(gain, sys_comp_module_gain_interface_init); #endif +#if CONFIG_STATIC_PIPELINE +#include + +#if CONFIG_IPC_MAJOR_4 +#include "peak_volume.h" + +struct static_ipc4_vol_init_cfg { + struct ipc4_base_module_cfg base_cfg; + struct ipc4_peak_volume_config config[1]; +}; +#endif + +static struct comp_dev *vol_static_create(const struct comp_driver *drv, + struct comp_ipc_config *cfg, + const struct sof_static_comp *cdesc, + uint32_t period_us) +{ +#if CONFIG_IPC_MAJOR_4 + struct static_ipc4_vol_init_cfg vol_cfg; + + memset(&vol_cfg, 0, sizeof(vol_cfg)); + sof_static_init_base_cfg(&vol_cfg.base_cfg, cdesc, period_us); + vol_cfg.config[0].channel_id = 0xffffffff; + vol_cfg.config[0].target_volume = 0x7FFFFFFF; + vol_cfg.config[0].curve_type = IPC4_AUDIO_CURVE_TYPE_WINDOWS_FADE; + vol_cfg.config[0].curve_duration = 100000; + + struct ipc_config_process spec = { + .size = sizeof(vol_cfg), + .data = (const uint8_t *)&vol_cfg, + }; + return drv->ops.create(drv, cfg, &spec); +#else + struct ipc_config_volume vol_cfg = { + .channels = cdesc->caps.max_channels ? cdesc->caps.max_channels : 2, + .min_value = 0, + .max_value = INT32_MAX, + .ramp = SOF_VOLUME_LINEAR, + .initial_ramp = 0, + }; + struct ipc_config_process spec = { + .size = sizeof(vol_cfg), + .data = (const uint8_t *)&vol_cfg, + }; + return drv->ops.create(drv, cfg, &spec); +#endif +} + +static int vol_static_apply_volume(struct comp_dev *dev, uint32_t channels, int32_t val) +{ + struct processing_module *mod = comp_mod(dev); + + if (!mod) + return -EINVAL; + + for (uint32_t ch = 0; ch < channels; ch++) + volume_set_chan(mod, ch, val, true); + + return 0; +} + +static int vol_static_apply_switch(struct comp_dev *dev, uint32_t channels, int32_t val) +{ + struct processing_module *mod = comp_mod(dev); + + if (!mod) + return -EINVAL; + + for (uint32_t ch = 0; ch < channels; ch++) { + if (val == 0) + volume_set_chan_mute(mod, ch); + else + volume_set_chan_unmute(mod, ch); + } + + return 0; +} + +static struct sof_static_module_ops vol_static_ops = { + .uuid = &volume_uuid, + .create = vol_static_create, + .apply_volume = vol_static_apply_volume, + .apply_switch = vol_static_apply_switch, +}; + +DECLARE_STATIC_MODULE_OPS(volume, &vol_static_ops); + +#if CONFIG_COMP_GAIN +static struct sof_static_module_ops gain_static_ops = { + .uuid = &gain_uuid, + .create = vol_static_create, + .apply_volume = vol_static_apply_volume, + .apply_switch = vol_static_apply_switch, +}; + +DECLARE_STATIC_MODULE_OPS(gain, &gain_static_ops); +#endif +#endif /* CONFIG_STATIC_PIPELINE */ + #endif diff --git a/src/include/sof/audio/component.h b/src/include/sof/audio/component.h index 48d0764f26d8..69c47d1c54c7 100644 --- a/src/include/sof/audio/component.h +++ b/src/include/sof/audio/component.h @@ -1021,6 +1021,19 @@ void comp_unregister(struct comp_driver_info *drv); */ int comp_set_adapter_ops(const struct comp_driver *drv, const struct module_interface *ops); +/** + * Look up a registered SOF component driver by UUID or driver type. + * + * Scans the global SOF component driver registry to match either a specific + * 128-bit UUID (for audio processing modules) or a generic driver type (for host + * or DAI endpoints). + * + * @param uuid Component UUID to match, or NULL to match by type. + * @param type SOF component driver type (SOF_COMP_HOST, SOF_COMP_DAI, etc.). + * @return Pointer to matched comp_driver, or NULL if not registered. + */ +const struct comp_driver *comp_driver_find(const struct sof_uuid *uuid, uint32_t type); + /** @}*/ /** diff --git a/src/include/sof/audio/pipeline/static_pipeline.h b/src/include/sof/audio/pipeline/static_pipeline.h new file mode 100644 index 000000000000..15bee800ba53 --- /dev/null +++ b/src/include/sof/audio/pipeline/static_pipeline.h @@ -0,0 +1,807 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2026 Sound Open Firmware (SOF) Project + */ + +/** + * \file include/sof/audio/pipeline/static_pipeline.h + * \brief Declarative Static Audio Pipeline Engine API + * \author Liam Girdwood + * + * \defgroup static_pipeline_api Static Audio Pipeline Engine + * @{ + * + * The Static Audio Pipeline subsystem provides compile-time and runtime + * declarative topology construction for hostless, microcontroller, and + * standalone embedded platforms running Sound Open Firmware. + * + * It allows platforms to construct complete SOF processing graphs (pipelines, + * modules, DAIs, intermediate buffers, routes, and kcontrols) directly at boot + * without requiring dynamic IPC topology commands from a host operating system. + */ + +#ifndef __SOF_AUDIO_PIPELINE_STATIC_PIPELINE_H__ +#define __SOF_AUDIO_PIPELINE_STATIC_PIPELINE_H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ========================================================================= + * Audio Format & Pipeline Capabilities + * ========================================================================= */ + +/** \brief Supported sample rate bitmasks for static capability negotiation */ +#define SOF_STATIC_RATE_8K SOF_RATE_8000 /**< 8000 Hz */ +#define SOF_STATIC_RATE_11K SOF_RATE_11025 /**< 11025 Hz */ +#define SOF_STATIC_RATE_12K SOF_RATE_12000 /**< 12000 Hz */ +#define SOF_STATIC_RATE_16K SOF_RATE_16000 /**< 16000 Hz */ +#define SOF_STATIC_RATE_22K SOF_RATE_22050 /**< 22050 Hz */ +#define SOF_STATIC_RATE_24K SOF_RATE_24000 /**< 24000 Hz */ +#define SOF_STATIC_RATE_32K SOF_RATE_32000 /**< 32000 Hz */ +#define SOF_STATIC_RATE_44K1 SOF_RATE_44100 /**< 44100 Hz */ +#define SOF_STATIC_RATE_48K SOF_RATE_48000 /**< 48000 Hz */ +#define SOF_STATIC_RATE_64K SOF_RATE_64000 /**< 64000 Hz */ +#define SOF_STATIC_RATE_88K2 SOF_RATE_88200 /**< 88200 Hz */ +#define SOF_STATIC_RATE_96K SOF_RATE_96000 /**< 96000 Hz */ +#define SOF_STATIC_RATE_176K4 SOF_RATE_176400 /**< 176400 Hz */ +#define SOF_STATIC_RATE_192K SOF_RATE_192000 /**< 192000 Hz */ +#define SOF_STATIC_RATE_384K (1 << 14) /**< 384000 Hz */ + +/** \brief Supported PCM frame format bitmasks (matching enum sof_ipc_frame) */ +#define SOF_STATIC_FMT_S16_LE BIT(SOF_IPC_FRAME_S16_LE) +#define SOF_STATIC_FMT_S24_4LE BIT(SOF_IPC_FRAME_S24_4LE) +#define SOF_STATIC_FMT_S32_LE BIT(SOF_IPC_FRAME_S32_LE) +#define SOF_STATIC_FMT_FLOAT BIT(SOF_IPC_FRAME_FLOAT) +#define SOF_STATIC_FMT_S24_3LE BIT(SOF_IPC_FRAME_S24_3LE) +#define SOF_STATIC_FMT_S24_4LE_MSB BIT(SOF_IPC_FRAME_S24_4LE_MSB) +#define SOF_STATIC_FMT_U8 BIT(SOF_IPC_FRAME_U8) +#define SOF_STATIC_FMT_S16_4LE BIT(SOF_IPC_FRAME_S16_4LE) +#define SOF_STATIC_FMT_A_LAW BIT(SOF_IPC_FRAME_A_LAW) +#define SOF_STATIC_FMT_MU_LAW BIT(SOF_IPC_FRAME_MU_LAW) + +/** Mask covering all supported sample rates */ +#define SOF_STATIC_RATE_MASK_ALL \ + (SOF_STATIC_RATE_8K | SOF_STATIC_RATE_11K | SOF_STATIC_RATE_12K | \ + SOF_STATIC_RATE_16K | SOF_STATIC_RATE_22K | SOF_STATIC_RATE_24K | \ + SOF_STATIC_RATE_32K | SOF_STATIC_RATE_44K1 | SOF_STATIC_RATE_48K | \ + SOF_STATIC_RATE_64K | SOF_STATIC_RATE_88K2 | SOF_STATIC_RATE_96K | \ + SOF_STATIC_RATE_176K4 | SOF_STATIC_RATE_192K | SOF_STATIC_RATE_384K) + +/** Mask covering all supported frame formats */ +#define SOF_STATIC_FMT_MASK_ALL \ + (SOF_STATIC_FMT_S16_LE | SOF_STATIC_FMT_S24_4LE | SOF_STATIC_FMT_S32_LE | \ + SOF_STATIC_FMT_FLOAT | SOF_STATIC_FMT_S24_3LE | SOF_STATIC_FMT_S24_4LE_MSB | \ + SOF_STATIC_FMT_U8 | SOF_STATIC_FMT_S16_4LE | SOF_STATIC_FMT_A_LAW | \ + SOF_STATIC_FMT_MU_LAW) + +/** + * \brief Audio stream format and sample rate capability descriptor. + */ +struct sof_static_caps { + uint32_t formats; /**< Bitmask of supported frame formats (SOF_STATIC_FMT_*) */ + uint32_t rates; /**< Bitmask of supported sample rates (SOF_STATIC_RATE_*) */ + uint16_t min_channels; /**< Minimum channel count */ + uint16_t max_channels; /**< Maximum channel count */ + uint32_t default_rate; /**< Default sample rate in Hz (e.g. 48000) */ + uint32_t sink_rate; /**< Target sink sample rate for converters (SRC/ASRC, 0 = same as default_rate) */ + enum sof_ipc_frame default_fmt; /**< Default frame format */ +}; + +/** + * \brief Helper macro to initialize a static capability structure. + * \param _fmt Frame format (e.g. SOF_IPC_FRAME_S16_LE or SOF_IPC_FRAME_FLOAT). + * \param _rate Default sample rate in Hz (e.g. 48000). + * \param _ch Number of audio channels (e.g. 2). + */ +#define SOF_STATIC_CAPS(_fmt, _rate, _ch) \ + { \ + .formats = BIT(_fmt), \ + .rates = (_rate == 8000) ? SOF_STATIC_RATE_8K : \ + (_rate == 11025) ? SOF_STATIC_RATE_11K : \ + (_rate == 12000) ? SOF_STATIC_RATE_12K : \ + (_rate == 16000) ? SOF_STATIC_RATE_16K : \ + (_rate == 22050) ? SOF_STATIC_RATE_22K : \ + (_rate == 24000) ? SOF_STATIC_RATE_24K : \ + (_rate == 32000) ? SOF_STATIC_RATE_32K : \ + (_rate == 44100) ? SOF_STATIC_RATE_44K1 : \ + (_rate == 48000) ? SOF_STATIC_RATE_48K : \ + (_rate == 64000) ? SOF_STATIC_RATE_64K : \ + (_rate == 88200) ? SOF_STATIC_RATE_88K2 : \ + (_rate == 96000) ? SOF_STATIC_RATE_96K : \ + (_rate == 176400) ? SOF_STATIC_RATE_176K4 : \ + (_rate == 192000) ? SOF_STATIC_RATE_192K : \ + (_rate == 384000) ? SOF_STATIC_RATE_384K : SOF_STATIC_RATE_48K, \ + .min_channels = (_ch), \ + .max_channels = (_ch), \ + .default_rate = (_rate), \ + .default_fmt = (_fmt), \ + } + +/* ========================================================================= + * Components / Processing Modules + * ========================================================================= */ + +/** + * \brief Category of a static pipeline component. + */ +enum sof_static_comp_type { + SOF_STATIC_COMP_MODULE = 0, /**< Audio processing module (Volume, EQ, DRC, etc.) */ + SOF_STATIC_COMP_HOST, /**< Host or USB streaming endpoint */ + SOF_STATIC_COMP_DAI, /**< Hardware Digital Audio Interface (I2S, PDM, SAI, SPDIF) */ +}; + +/** + * \brief Hardware endpoint binding type. + */ +enum sof_static_ep_type { + SOF_STATIC_EP_NONE = 0, /**< Standard internal processing module */ + SOF_STATIC_EP_USB_TERMINAL, /**< USB Audio Class 2 (UAC2) streaming terminal */ + SOF_STATIC_EP_DAI, /**< Physical hardware DAI controller */ +}; + +/** + * \brief Declarative component descriptor. + */ +struct sof_static_comp { + uint32_t id; /**< Unique component identifier */ + uint32_t pipeline_id; /**< Owning pipeline identifier */ + const char *name; /**< Human-readable diagnostic name (e.g. "VOL_PB") */ + enum sof_static_comp_type type; /**< Component category: Module, Host, or DAI */ + const struct sof_uuid *uuid; /**< Registered driver UUID (NULL for standard Host or DAI) */ + uint32_t direction; /**< Audio stream direction: SOF_IPC_STREAM_PLAYBACK or CAPTURE */ + struct sof_static_caps caps; /**< Format and sample rate capabilities */ + + enum sof_static_ep_type ep_type; /**< Endpoint binding type */ + union { + struct { + uint32_t terminal_id; /**< Bound UAC2 USB Terminal Entity ID */ + } usb; + struct { + uint32_t dai_type; /**< DAI type (e.g. SOF_DAI_ESP32_I2S, SOF_DAI_IMX_SAI) */ + uint32_t dai_index; /**< DAI hardware instance index */ + uint32_t format; /**< Protocol/clock format mask (SOF_DAI_FMT_*) */ + int (*configure)(struct comp_dev *dev, + struct ipc_config_dai *dai_cfg, + struct sof_ipc_dai_config *spec_cfg); /**< Optional platform DAI endpoint config hook */ + } dai; + } ep; + + const void *init_blob; /**< Optional initial ABI configuration blob */ + size_t init_blob_size; /**< Size of initial configuration blob in bytes */ +}; + +/** + * \brief Declare a processing module component. + */ +#define SOF_STATIC_COMP_MODULE(...) \ + { .type = SOF_STATIC_COMP_MODULE, __VA_ARGS__ } + +/** + * \brief Declare a USB/Host streaming endpoint component. + */ +#define SOF_STATIC_COMP_HOST(...) \ + { .type = SOF_STATIC_COMP_HOST, .ep_type = SOF_STATIC_EP_USB_TERMINAL, __VA_ARGS__ } + +/** + * \brief Declare a hardware DAI endpoint component. + */ +#define SOF_STATIC_COMP_DAI(...) \ + { .type = SOF_STATIC_COMP_DAI, .ep_type = SOF_STATIC_EP_DAI, __VA_ARGS__ } + +/* ========================================================================= + * Module Constructor Macros + * ========================================================================= */ + +/** + * \brief Generic constructor macro for an audio processing module component. + * + * \param _id Unique component ID. + * \param _ppl Owning pipeline ID. + * \param _name Human-readable component name string. + * \param _dir Stream direction (SOF_IPC_STREAM_PLAYBACK or SOF_IPC_STREAM_CAPTURE). + * \param _uuid Pointer to component driver UUID (e.g. &volume_uuid). + * \param _fmt Default PCM frame format (enum sof_ipc_frame). + * \param _rate Default stream sample rate in Hz. + * \param _ch Default channel count. + * \param _blob Optional pointer to initial configuration blob (or NULL). + * \param _blob_sz Size of initial configuration blob in bytes (or 0). + */ +#define SOF_STATIC_MODULE(_id, _ppl, _name, _dir, _uuid, _fmt, _rate, _ch, _blob, _blob_sz) \ + { \ + .id = (_id), \ + .pipeline_id = (_ppl), \ + .name = (_name), \ + .type = SOF_STATIC_COMP_MODULE, \ + .uuid = (_uuid), \ + .direction = (_dir), \ + .caps = SOF_STATIC_CAPS((_fmt), (_rate), (_ch)), \ + .init_blob = (_blob), \ + .init_blob_size = (_blob_sz), \ + } + +/** + * \brief Generic constructor macro for a sample rate converter component (SRC / ASRC). + * + * \param _id Unique component ID. + * \param _ppl Owning pipeline ID. + * \param _name Human-readable component name string. + * \param _dir Stream direction (SOF_IPC_STREAM_PLAYBACK or SOF_IPC_STREAM_CAPTURE). + * \param _uuid Pointer to component driver UUID (e.g. &src_uuid, &asrc_uuid). + * \param _fmt Default PCM frame format (enum sof_ipc_frame). + * \param _in_rate Source/input sample rate in Hz. + * \param _out_rate Sink/output sample rate in Hz. + * \param _ch Channel count. + */ +#define SOF_STATIC_MODULE_RATE_CONV(_id, _ppl, _name, _dir, _uuid, _fmt, _in_rate, _out_rate, _ch) \ + { \ + .id = (_id), \ + .pipeline_id = (_ppl), \ + .name = (_name), \ + .type = SOF_STATIC_COMP_MODULE, \ + .uuid = (_uuid), \ + .direction = (_dir), \ + .caps = { \ + .formats = BIT(_fmt), \ + .rates = SOF_STATIC_RATE_MASK_ALL, \ + .min_channels = (_ch), \ + .max_channels = (_ch), \ + .default_rate = (_in_rate), \ + .sink_rate = (_out_rate), \ + .default_fmt = (_fmt), \ + }, \ + } + +/* ========================================================================= + * Endpoint Constructor Macros + * ========================================================================= */ + +/** + * \brief Simple constructor macro for a USB / UAC2 streaming endpoint. + * + * \param _id Unique component ID. + * \param _ppl Owning pipeline ID. + * \param _name Human-readable component name. + * \param _dir Stream direction (SOF_IPC_STREAM_PLAYBACK or SOF_IPC_STREAM_CAPTURE). + * \param _fmt Default PCM frame format (enum sof_ipc_frame). + * \param _rate Stream sample rate in Hz. + * \param _ch Channel count. + * \param _term_id USB Audio Class Terminal ID. + */ +#define SOF_STATIC_ENDPOINT_USB(_id, _ppl, _name, _dir, _fmt, _rate, _ch, _term_id) \ + { \ + .id = (_id), \ + .pipeline_id = (_ppl), \ + .name = (_name), \ + .type = SOF_STATIC_COMP_HOST, \ + .direction = (_dir), \ + .caps = SOF_STATIC_CAPS((_fmt), (_rate), (_ch)), \ + .ep_type = SOF_STATIC_EP_USB_TERMINAL, \ + .ep.usb.terminal_id = (_term_id), \ + } + +/** + * \brief Generic constructor macro for a hardware Digital Audio Interface (DAI) endpoint. + * + * \param _id Unique component ID. + * \param _ppl Owning pipeline ID. + * \param _name Human-readable component name. + * \param _dir Stream direction (SOF_IPC_STREAM_PLAYBACK or SOF_IPC_STREAM_CAPTURE). + * \param _fmt Default PCM frame format (enum sof_ipc_frame). + * \param _rate Stream sample rate in Hz. + * \param _ch Channel count. + * \param _type Hardware DAI type (e.g. SOF_DAI_INTEL_SSP, SOF_DAI_ESP32_I2S, etc.). + * \param _idx Hardware DAI controller instance index. + * \param _clk_fmt DAI clock format flags (e.g. SOF_DAI_FMT_I2S | SOF_DAI_FMT_CBC_CFC). + */ +#define SOF_STATIC_ENDPOINT_DAI(_id, _ppl, _name, _dir, _fmt, _rate, _ch, _type, _idx, _clk_fmt) \ + { \ + .id = (_id), \ + .pipeline_id = (_ppl), \ + .name = (_name), \ + .type = SOF_STATIC_COMP_DAI, \ + .direction = (_dir), \ + .caps = SOF_STATIC_CAPS((_fmt), (_rate), (_ch)), \ + .ep_type = SOF_STATIC_EP_DAI, \ + .ep.dai.dai_type = (_type), \ + .ep.dai.dai_index = (_idx), \ + .ep.dai.format = (_clk_fmt), \ + } + +/** + * \brief Constructor macro for a hardware DAI endpoint with custom platform configuration callback. + * + * \param _id Unique component ID. + * \param _ppl Owning pipeline ID. + * \param _name Human-readable component name. + * \param _dir Stream direction (SOF_IPC_STREAM_PLAYBACK or SOF_IPC_STREAM_CAPTURE). + * \param _fmt Default PCM frame format (enum sof_ipc_frame). + * \param _rate Stream sample rate in Hz. + * \param _ch Channel count. + * \param _type Hardware DAI type (e.g. SOF_DAI_INTEL_SSP, SOF_DAI_ESP32_I2S, etc.). + * \param _idx Hardware DAI controller instance index. + * \param _clk_fmt DAI clock format flags (e.g. SOF_DAI_FMT_I2S | SOF_DAI_FMT_CBC_CFC). + * \param _cfg_fn Endpoint configuration callback function pointer. + */ +#define SOF_STATIC_ENDPOINT_DAI_CFG(_id, _ppl, _name, _dir, _fmt, _rate, _ch, _type, _idx, _clk_fmt, _cfg_fn) \ + { \ + .id = (_id), \ + .pipeline_id = (_ppl), \ + .name = (_name), \ + .type = SOF_STATIC_COMP_DAI, \ + .direction = (_dir), \ + .caps = SOF_STATIC_CAPS((_fmt), (_rate), (_ch)), \ + .ep_type = SOF_STATIC_EP_DAI, \ + .ep.dai.dai_type = (_type), \ + .ep.dai.dai_index = (_idx), \ + .ep.dai.format = (_clk_fmt), \ + .ep.dai.configure = (_cfg_fn), \ + } + +/* ========================================================================= + * Static Module Operations + * ========================================================================= */ + +/** + * \brief Static pipeline module driver operations. + * + * Audio processing modules register these operations to handle static + * instantiation (synthesizing any module-specific IPC init configuration) + * and runtime kcontrol manipulation (volume, switch/mute, enum) without + * requiring the generic pipeline loader to contain module-specific logic. + */ +struct sof_static_module_ops { + /** UUID of target audio component */ + const struct sof_uuid *uuid; + + /** + * \brief Instantiate and initialize the module component. + * \param[in] drv SOF component driver. + * \param[in] cfg IPC component configuration. + * \param[in] cdesc Static component descriptor. + * \param[in] period_us Owning pipeline scheduling period in microseconds. + * \return Created component device pointer, or NULL on error. + */ + struct comp_dev *(*create)(const struct comp_driver *drv, + struct comp_ipc_config *cfg, + const struct sof_static_comp *cdesc, + uint32_t period_us); + + /** + * \brief Apply linear volume gain to component. + * \param[in] dev Component device pointer. + * \param[in] channels Channel count to apply volume across. + * \param[in] val Linear volume value (0 to INT32_MAX). + * \return 0 on success, negative error code on failure. + */ + int (*apply_volume)(struct comp_dev *dev, uint32_t channels, int32_t val); + + /** + * \brief Apply binary switch/mute state to component. + * \param[in] dev Component device pointer. + * \param[in] channels Channel count. + * \param[in] val Binary switch value (0 = muted/bypass, 1 = unmuted/active). + * \return 0 on success, negative error code on failure. + */ + int (*apply_switch)(struct comp_dev *dev, uint32_t channels, int32_t val); + + /** + * \brief Apply enumerated route/value to component. + * \param[in] dev Component device pointer. + * \param[in] channel Channel index. + * \param[in] val Selected enumeration value. + * \return 0 on success, negative error code on failure. + */ + int (*apply_enum)(struct comp_dev *dev, uint32_t channel, int32_t val); + + /** List node for registered ops */ + struct list_item list; +}; + +/** + * \brief Macro to automatically register static module operations at system startup. + * + * In Zephyr RTOS environments, registers using SYS_INIT at APPLICATION level. + * In non-Zephyr environments (e.g. POSIX tests), registers using constructor attribute. + * + * \param name Unique identifier suffix for initialization routine. + * \param ops Pointer to struct sof_static_module_ops. + */ +#if defined(__ZEPHYR__) +#include +#define DECLARE_STATIC_MODULE_OPS(name, ops) \ + static int _static_ops_init_##name(void) \ + { \ + return sof_static_register_module_ops(ops); \ + } \ + SYS_INIT(_static_ops_init_##name, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY) +#else +#define DECLARE_STATIC_MODULE_OPS(name, ops) \ + __attribute__((constructor)) __used \ + static void _static_ops_init_##name(void) \ + { \ + sof_static_register_module_ops(ops); \ + } +#endif + +/* ========================================================================= + * Intermediate Audio Buffers & Pipeline Routing + * ========================================================================= */ + +/** + * \brief Declarative intermediate audio buffer descriptor. + */ +struct sof_static_buffer { + uint32_t id; /**< Unique buffer identifier */ + size_t size; /**< Total buffer allocation size in bytes */ + enum sof_ipc_frame fmt; /**< Buffer audio frame format */ + uint32_t rate; /**< Sample rate in Hz (0 = inherit from producer component) */ + uint16_t channels; /**< Channel count (0 = inherit from producer component) */ + uint32_t flags; /**< Memory allocation flags (e.g. SOF_MEM_FLAG_DMA) */ +}; + +/** + * \brief Helper macro to declare an intermediate audio buffer with standard flags. + */ +#define SOF_STATIC_BUFFER(...) \ + { .flags = SOF_MEM_FLAG_DMA | SOF_MEM_FLAG_USER, __VA_ARGS__ } + +/** + * \brief Declarative graph route connecting an upstream component to a downstream component. + */ +struct sof_static_route { + uint32_t src_comp_id; /**< Upstream producer component ID */ + uint32_t buffer_id; /**< Intermediate buffer ID linking the components */ + uint32_t sink_comp_id; /**< Downstream consumer component ID */ +}; + +/** + * \brief Helper macro to declare a connection route. + */ +#define SOF_STATIC_ROUTE(...) \ + { __VA_ARGS__ } + +/* ========================================================================= + * PCMs (Pulse Code Modulated Endpoints) + * ========================================================================= */ + +/** + * \brief Declarative PCM endpoint descriptor. + */ +struct sof_static_pcm { + uint32_t pcm_id; /**< Logical PCM stream index */ + const char *name; /**< Human-readable PCM name (e.g. "Speaker Playback") */ + uint32_t direction; /**< Audio direction: SOF_IPC_STREAM_PLAYBACK or CAPTURE */ + uint32_t pipeline_id; /**< Associated pipeline ID */ + uint32_t host_comp_id; /**< Host/USB endpoint component ID */ + struct sof_static_caps caps; /**< Supported PCM capabilities */ +}; + +/** + * \brief Helper macro to declare a static PCM stream. + */ +#define SOF_STATIC_PCM(...) \ + { __VA_ARGS__ } + +/* ========================================================================= + * Kcontrols (Volume, Mute, Bypass, Presets, Coefficients) + * ========================================================================= */ + +/** + * \brief Type of static kcontrol. + */ +enum sof_static_ctrl_type { + SOF_STATIC_CTRL_VOLUME = 0, /**< Linear or dB volume fader */ + SOF_STATIC_CTRL_SWITCH, /**< Boolean mute or module bypass switch */ + SOF_STATIC_CTRL_ENUM, /**< Multi-value enumeration selector */ + SOF_STATIC_CTRL_BINARY, /**< Raw binary parameter or coefficient blob */ +}; + +/** + * \brief Declarative static kcontrol descriptor. + */ +struct sof_static_kcontrol { + uint32_t id; /**< Unique control identifier */ + const char *name; /**< Human-readable control name (e.g. "Master Volume") */ + enum sof_static_ctrl_type type; /**< Control type */ + uint32_t target_comp_id; /**< Target component ID affected by this control (0 for custom) */ + uint32_t param_id; /**< Optional parameter or command index */ + + int32_t min; /**< Minimum valid value */ + int32_t max; /**< Maximum valid value */ + int32_t def; /**< Default initial value applied at boot */ + uint32_t channels; /**< Number of channels affected by this control */ + + uint8_t uac2_entity_id; /**< Bound external entity ID (e.g. UAC2 Feature Unit) */ +}; + +/** \brief Helper macro to declare a volume kcontrol */ +#define SOF_STATIC_KCONTROL_VOLUME(...) \ + { .type = SOF_STATIC_CTRL_VOLUME, __VA_ARGS__ } + +/** \brief Helper macro to declare a boolean switch/bypass kcontrol */ +#define SOF_STATIC_KCONTROL_SWITCH(...) \ + { .type = SOF_STATIC_CTRL_SWITCH, __VA_ARGS__ } + +/** \brief Helper macro to declare an enumeration selector kcontrol */ +#define SOF_STATIC_KCONTROL_ENUM(...) \ + { .type = SOF_STATIC_CTRL_ENUM, __VA_ARGS__ } + +/** \brief Helper macro to declare a binary configuration kcontrol */ +#define SOF_STATIC_KCONTROL_BINARY(...) \ + { .type = SOF_STATIC_CTRL_BINARY, __VA_ARGS__ } + +/* ========================================================================= + * Pipeline Descriptors & Top-Level Topology + * ========================================================================= */ + +/** + * \brief Declarative audio pipeline descriptor. + */ +struct sof_static_pipeline_desc { + uint32_t pipeline_id; /**< Unique pipeline identifier */ + const char *name; /**< Human-readable pipeline name */ + uint32_t direction; /**< Audio direction: SOF_IPC_STREAM_PLAYBACK or CAPTURE */ + uint32_t priority; /**< Scheduling priority (0 = normal) */ + uint32_t core; /**< Core affinity index (0, 1, ...) */ + uint32_t period; /**< Scheduling period in microseconds (e.g. 1000) */ + uint32_t frames_per_sched; /**< Number of frames processed per schedule tick */ + uint32_t time_domain; /**< Scheduling time domain (SOF_TIME_DOMAIN_TIMER or DMA) */ + uint32_t default_rate; /**< Default stream rate in Hz (e.g. 48000, 0 = auto) */ + uint16_t default_channels; /**< Default channel count (e.g. 2, 0 = auto) */ + uint32_t sched_comp_id; /**< Component driving pipeline scheduling */ + uint32_t source_comp_id; /**< Primary source endpoint component ID */ + uint32_t sink_comp_id; /**< Primary sink endpoint component ID */ +}; + +/** + * \brief Custom kcontrol callback handler type. + * + * Allows platforms or applications to register custom kcontrol dispatch + * logic for non-module controls (e.g. clock modes, routes, injector switches). + * + * \param[in] ctl Pointer to the static kcontrol descriptor. + * \param[in] val Value to write. + * \param[in,out] priv Private user/platform context. + * \return 0 on success, negative errno on failure. + */ +typedef int (*sof_static_kcontrol_handler_fn)(const struct sof_static_kcontrol *ctl, + int32_t val, void *priv); + +/** + * \brief Top-level static audio topology descriptor. + */ +struct sof_static_topology { + const char *name; /**< Human-readable topology name */ + size_t num_pipelines; /**< Number of pipelines in this topology */ + const struct sof_static_pipeline_desc *pipelines; /**< Array of pipeline descriptors */ + size_t num_comps; /**< Number of components */ + const struct sof_static_comp *comps; /**< Array of component descriptors */ + size_t num_buffers; /**< Number of intermediate buffers */ + const struct sof_static_buffer *buffers; /**< Array of buffer descriptors */ + size_t num_routes; /**< Number of graph connection routes */ + const struct sof_static_route *routes; /**< Array of connection routes */ + size_t num_pcms; /**< Number of logical PCMs */ + const struct sof_static_pcm *pcms; /**< Array of PCM descriptors */ + size_t num_controls; /**< Number of kcontrols */ + const struct sof_static_kcontrol *controls; /**< Array of kcontrol descriptors */ + + sof_static_kcontrol_handler_fn custom_control_handler; /**< Optional platform control hook */ + void *custom_control_data; /**< Platform context passed to hook */ +}; + +/* ========================================================================= + * Generic Engine Public APIs + * ========================================================================= */ + +/** + * \brief Initialize and instantiate the static topology graph. + * + * Allocates pipelines, instantiates components from the driver registry, + * initializes intermediate buffers, establishes graph connections, negotiates + * component stream parameters, and prepares all pipelines for streaming. + * + * \param[in] topo Pointer to the target static topology definition. + * \return 0 on success, negative errno on failure. + */ +int sof_static_topology_init(const struct sof_static_topology *topo); + +/** + * \brief Retrieve the currently active static topology descriptor. + * \return Pointer to active topology descriptor, or NULL if uninitialized. + */ +const struct sof_static_topology *sof_static_topology_get(void); + +/** + * \brief Retrieve a pipeline instance by its pipeline ID. + * \param[in] pipeline_id Unique pipeline ID. + * \return Pointer to struct pipeline, or NULL if not found. + */ +struct pipeline *sof_static_pipeline_get(uint32_t pipeline_id); + +/** + * \brief Retrieve a component device instance by its component ID. + * \param[in] comp_id Unique component ID. + * \return Pointer to struct comp_dev, or NULL if not found. + */ +struct comp_dev *sof_static_comp_get(uint32_t comp_id); + +/** + * \brief Retrieve an intermediate buffer instance by its buffer ID. + * \param[in] buffer_id Unique buffer ID. + * \return Pointer to struct comp_buffer, or NULL if not found. + */ +struct comp_buffer *sof_static_buffer_get(uint32_t buffer_id); + +/** + * \brief Register static module operations for an audio component. + * + * Audio processing modules register static operations at boot or module load + * time to handle static component instantiation and kcontrol dispatch without + * hardcoded component logic in the generic pipeline engine. + * + * \param[in,out] ops Pointer to module operations structure. + * \return 0 on success, negative error code on failure. + */ +int sof_static_register_module_ops(struct sof_static_module_ops *ops); + +/** + * \brief Find registered static module operations by component UUID. + * \param[in] uuid UUID of target component. + * \return Pointer to registered ops, or NULL if not found. + */ +const struct sof_static_module_ops *sof_static_find_module_ops(const struct sof_uuid *uuid); + +struct ipc4_base_module_cfg; + +/** + * \brief Synthesize standard IPC4 base module configuration structure. + * \param[out] base_cfg Target base configuration to populate. + * \param[in] cdesc Static component descriptor with format and rate capabilities. + * \param[in] period_us Pipeline scheduling period in microseconds. + */ +void sof_static_init_base_cfg(struct ipc4_base_module_cfg *base_cfg, + const struct sof_static_comp *cdesc, + uint32_t period_us); + +/** + * \brief Default fallback creation for standard IPC4 module adapters. + * \param[in] drv SOF component driver. + * \param[in] cfg IPC component configuration. + * \param[in] cdesc Static component descriptor. + * \param[in] period_us Owning pipeline scheduling period in microseconds. + * \return Created comp_dev pointer, or NULL on failure. + */ +struct comp_dev *sof_static_module_create_default(const struct comp_driver *drv, + struct comp_ipc_config *cfg, + const struct sof_static_comp *cdesc, + uint32_t period_us); + +/** + * \brief Set kcontrol value by control ID. + * + * Dispatches to the target module via its registered static operations + * (struct sof_static_module_ops), or invokes the topology's custom_control_handler. + * + * \param[in] ctrl_id Unique control ID. + * \param[in] val Value to apply. + * \return 0 on success, negative errno on failure. + */ +int sof_static_kcontrol_set(uint32_t ctrl_id, int32_t val); + +/** + * \brief Retrieve cached kcontrol value by control ID. + * \param[in] ctrl_id Unique control ID. + * \param[out] val Pointer to store retrieved control value. + * \return 0 on success, negative errno on failure. + */ +int sof_static_kcontrol_get(uint32_t ctrl_id, int32_t *val); + +/** + * \brief Find kcontrol ID by human-readable name. + * \param[in] name Control name string to search for. + * \return Control ID >= 0 if found, -ENOENT if not found. + */ +int sof_static_kcontrol_find_by_name(const char *name); + +/** + * \brief Dispatch UAC2 feature unit control to registered kcontrols. + * \param[in] entity_id UAC2 Feature Unit Entity ID. + * \param[in] channel Audio channel index (0 = master/all). + * \param[in] val Value from USB request (e.g. 8.8 fixed-point dB volume or boolean mute). + * \param[in] is_volume True for volume command, false for mute command. + * \return 0 on success, negative errno on failure. + */ +int sof_static_kcontrol_set_by_uac2(uint8_t entity_id, uint8_t channel, int32_t val, bool is_volume); + +/** + * \brief Retrieve kcontrol value formatted for UAC2 feature unit response. + * \param[in] entity_id UAC2 Feature Unit Entity ID. + * \param[in] channel Audio channel index. + * \param[out] val Pointer to store retrieved value. + * \param[in] is_volume True for volume query, false for mute query. + * \return 0 on success, negative errno on failure. + */ +int sof_static_kcontrol_get_by_uac2(uint8_t entity_id, uint8_t channel, int32_t *val, bool is_volume); + +/** + * \brief Trigger pipeline start or stop associated with a UAC2 terminal ID. + * \param[in] terminal_id Bound UAC2 Terminal Entity ID. + * \param[in] start True to start pipeline, false to pause/stop. + * \return 0 on success, negative errno on failure. + */ +int sof_static_pipeline_trigger_by_uac2_term(uint8_t terminal_id, bool start); + +/** + * \brief Start a static pipeline by pipeline ID. + * + * Prepares the pipeline and constituent components if needed, propagates + * start triggers, transitions state to COMP_STATE_ACTIVE, and activates + * scheduling copy tasks. + * + * \param[in] pipeline_id Target pipeline ID. + * \return 0 on success, negative errno on failure. + */ +int sof_static_pipeline_start(uint32_t pipeline_id); + +/** + * \brief Stop a static pipeline by pipeline ID. + * + * Propagates stop triggers, cancels scheduling copy tasks, and transitions + * pipeline state to COMP_STATE_PAUSED. + * + * \param[in] pipeline_id Target pipeline ID. + * \return 0 on success, negative errno on failure. + */ +int sof_static_pipeline_stop(uint32_t pipeline_id); + +/** + * \brief Directly trigger a static pipeline start or stop by pipeline ID. + * + * Synchronously transitions the pipeline state machine, propagates triggers + * to all constituent components, and manages scheduling task execution. + * Dispatches directly to sof_static_pipeline_start() or sof_static_pipeline_stop(). + * + * \param[in] pipeline_id Target pipeline ID. + * \param[in] start True to start pipeline, false to stop. + * \return 0 on success, negative errno on failure. + */ +int sof_static_pipeline_trigger(uint32_t pipeline_id, bool start); + +/** + * \brief Set global sample rate across active static pipelines. + * + * In pipelines with sample rate converters (SRC / ASRC), individual pipeline + * sections maintain their distinct sample rates according to their component + * capabilities and buffer configurations. + * + * \param[in] rate Target primary sample rate in Hz (e.g. 48000). + * \return 0 on success. + */ +int sof_static_set_sample_rate(uint32_t rate); + +/** + * \brief Retrieve current primary sample rate. + * \return Current sample rate in Hz. + */ +uint32_t sof_static_get_sample_rate(void); + +#ifdef __cplusplus +} +#endif + +#endif /* __SOF_AUDIO_PIPELINE_STATIC_PIPELINE_H__ */ + +/** @} */