Skip to content

audio: pipeline: fix control init order and unrouted component prepare in static pipeline - #11315

Open
lgirdwood wants to merge 2 commits into
thesofproject:mainfrom
lgirdwood:topic/static-pipeline-fixes
Open

lgirdwood wants to merge 2 commits into
thesofproject:mainfrom
lgirdwood:topic/static-pipeline-fixes

Conversation

@lgirdwood

@lgirdwood lgirdwood commented Oct 15, 2026 •

Copy link
Copy Markdown
Member

Description

This PR fixes two initialization and prepare issues in the declarative static pipeline loader subsystem (static_pipeline_loader.c), which operates strictly under IPC4 (CONFIG_IPC_MAJOR_4=y):

  1. Premature kcontrol initialization order in sof_static_topology_init():

    • Stage 7 (sof_static_topology_init_controls()) dispatches default control values via sof_static_kcontrol_set(), which verifies if (!s_active_topo) return -ENODEV;.
    • s_active_topo was assigned only after Stage 7 succeeded, causing all initial kcontrol configurations at boot to fail with -ENODEV (-19).
    • Fix: Publish s_active_topo = topo; prior to Stage 7, and reset it to NULL in err_unwind if topology construction fails.
  2. Unrouted component prepare failure:

    • In Stage 6 (sof_static_topology_prepare_comps()), comp_prepare() is called on all components in COMP_STATE_READY. For optional or unrouted components declared in a static topology that have no connected buffers (bsource_list and bsink_list both empty), comp_prepare() fails with -ENOTCONN (-107).
    • Fix: Skip components without any buffer connections in the stage 6 prepare loop.

Changes

  • src/audio/pipeline/static_pipeline_loader.c:
    • Set s_active_topo = topo; before calling sof_static_topology_init_controls(), and clear to NULL on error unwind.
    • Skip prepare on components where list_is_empty(&dev->bsource_list) && list_is_empty(&dev->bsink_list).

All static pipeline logic remains strictly IPC4-based with zero IPC3 dependencies.

@lgirdwood
lgirdwood force-pushed the topic/static-pipeline-fixes branch from 9084313 to a0838bc Compare October 15, 2026 18:25
@lgirdwood lgirdwood changed the title audio: pipeline: fix IPC3 support and control init in static pipeline audio: pipeline: fix control init order and unrouted component prepare in static pipeline Oct 15, 2026
/* Only prepare components currently in READY state */
/* Only prepare components currently in READY state that are connected to buffers */
if (dev->state == COMP_STATE_READY) {
if (list_is_empty(&dev->bsource_list) && list_is_empty(&dev->bsink_list))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Could we check this for the sink/source buffer APIs instead of relying on comp_buffer?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

will do, I want everything using the new API too

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Updated and rebased on main:

  • Replaced the dev->bsource_list / dev->bsink_list check in sof_static_topology_prepare_comps() with sof_static_comp_has_sink_source(), which checks mod->num_of_sources > 0 || mod->num_of_sinks > 0 for module adapter components and source_get_comp_state() / sink_get_state() on sof_audio_buffer handles for endpoint components.
  • Added comp_bind() / comp_unbind() in sof_static_topology_init_routes() and sof_static_topology_unwind() so sof_sink and sof_source interfaces are bound to IPC4 processing_module instances before prepare().
  • Converted init_buffer_params(), sof_static_topology_unwind(), and sof_static_pipeline_set_sample_rate() to use the sof_sink, sof_source, and sof_audio_buffer APIs (sink_set_params(), source_set_params(), audio_buffer_reset(), audio_buffer_free(), source_get_frm_fmt(), source_get_channels()).

@lgirdwood
lgirdwood force-pushed the topic/static-pipeline-fixes branch from a0838bc to 6cd4592 Compare November 1, 2026 20:37
@lgirdwood
lgirdwood marked this pull request as ready for review November 4, 2026 16:28
Copilot AI balanced review requested due to automatic review settings November 4, 2026 16:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Critical loader defects can corrupt memory when connection limits are exceeded, misconfigure SRC/ASRC pipeline rates, and prevent static mixin-to-mixout routes from binding.

3 open issues
What changed in this PR

This PR updates SOF’s IPC4 static pipeline loader to initialize controls after publishing the topology and skip preparation of unconnected components. It also extends buffer binding, cleanup, and sample-rate handling.

Changes:

  • Publish the active topology before control initialization and clear it on failure.
  • Bind sink/source interfaces to routed components and unbind them during cleanup.
  • Use buffer APIs for initialization and update module configurations and buffer thresholds during sample-rate changes.
File Description
src/​audio/​pipeline/​static_pipeline_loader.c Updates initialization order, connection-aware preparation, interface binding and cleanup, and sample-rate configuration.

Review effort: Balanced

if (topo->routes[j].sink_comp_id == r->sink_comp_id)
dst_queue++;
}
buf_sink->audio_stream_params->id = IPC4_COMP_ID(src_queue, dst_queue);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 80c0fb7 — added per-component queue bounds validation against CONFIG_MODULE_MAX_CONNECTIONS, 3-bit IPC4 queue ID limits, and src_mod->max_sinks / sink_mod->max_sources before pipeline_connect() and comp_bind().


bind_data.bind_type = COMP_BIND_TYPE_SOURCE;
bind_data.source = buf_source;
ret = comp_bind(sink, &bind_data);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 80c0fb7 — static components are now registered in ipc_get()->comp_list (with fallback IPC initialization when needed) during sof_static_topology_init_comps() and removed via list_item_del() in sof_static_topology_unwind(), allowing ipc4_get_comp_dev() to resolve endpoints in mixout_bind()/mixout_unbind().

Comment on lines +1953 to +1957
bcfg->audio_fmt.sampling_frequency = rate;
if (frame_bytes) {
bcfg->ibs = frames * frame_bytes;
if (!cdesc->caps.sink_rate)
bcfg->obs = bcfg->ibs;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 80c0fb7 — added sof_static_resolve_comp_in_rate() so components and route buffers downstream of a fixed-output rate converter (caps.sink_rate != 0) preserve sink_rate during validation, module base_cfg updates, and route buffer reconfiguration.

@intel-sofci

intel-sofci commented Nov 4, 2026 •

Copy link
Copy Markdown

PR 11315: test results

Run date: 2026-11-04 20:03 UTC

Tested commit: 80c0fb7aaa48e0d426c01bbd724c6591ec9726db

mtl pass rate lnl pass rate ptl pass rate wcl pass rate nvl pass rate

Comment on lines +963 to +997
if (dev_comp_type(dev) == SOF_COMP_MODULE_ADAPTER) {
struct processing_module *mod = comp_mod(dev);

return mod && (mod->num_of_sources > 0 || mod->num_of_sinks > 0);
}

if (!topo)
return false;

uint32_t comp_id = dev_comp_id(dev);

for (size_t i = 0; i < topo->num_routes; i++) {
const struct sof_static_route *r = &topo->routes[i];

if (r->src_comp_id != comp_id && r->sink_comp_id != comp_id)
continue;

struct comp_buffer *buf = sof_static_buffer_get(r->buffer_id);
if (!buf)
continue;

if (r->src_comp_id == comp_id) {
struct sof_source *source = audio_buffer_get_source(&buf->audio_buffer);

if (source && source_get_comp_state(source) != COMP_STATE_NOT_EXIST)
return true;
}

if (r->sink_comp_id == comp_id) {
struct sof_sink *sink = audio_buffer_get_sink(&buf->audio_buffer);

if (sink && sink_get_state(sink) != COMP_STATE_NOT_EXIST)
return true;
}
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

lets inline comment the block and decisions here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done in 80c0fb7 — added inline comments documenting both the IPC4 processing-module path (mod->num_of_sources / mod->num_of_sinks) and the endpoint component fallback (source_get_comp_state() / sink_get_state()).

Comment on lines +1166 to +1194
if (src) {
bu.primary.r.module_id = IPC4_MOD_ID(dev_comp_id(src));
bu.primary.r.instance_id = IPC4_INST_ID(dev_comp_id(src));
}
if (sink) {
bu.extension.r.dst_module_id = IPC4_MOD_ID(dev_comp_id(sink));
bu.extension.r.dst_instance_id = IPC4_INST_ID(dev_comp_id(sink));
}

if (src && buf_sink) {
struct bind_info unbind_src = {
.ipc4_data = &bu,
.bind_type = COMP_BIND_TYPE_SINK,
.sink = buf_sink,
};
comp_unbind(src, &unbind_src);
pipeline_disconnect(src, buf, PPL_CONN_DIR_COMP_TO_BUFFER);
}
if (sink && buf_source) {
struct bind_info unbind_sink = {
.ipc4_data = &bu,
.bind_type = COMP_BIND_TYPE_SOURCE,
.source = buf_source,
};
comp_unbind(sink, &unbind_sink);
pipeline_disconnect(sink, buf, PPL_CONN_DIR_BUFFER_TO_COMP);
}

audio_buffer_free(&buf->audio_buffer);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

can we inline comment each phase here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done in 80c0fb7 — added inline comments for each teardown phase in sof_static_topology_unwind() (Phases 1–5: endpoint resolution, queue recalculation, comp_unbind(), pipeline_disconnect() + audio_buffer_free(), and IPC registry + component/pipeline destruction).

Comment on lines +1941 to +1960
if (cdev && dev_comp_type(cdev) == SOF_COMP_MODULE_ADAPTER) {
struct processing_module *mod = comp_mod(cdev);

if (mod) {
struct ipc4_base_module_cfg *bcfg = &mod->priv.cfg.base_cfg;
uint32_t period_us = cdev->period ? cdev->period : 1000;
uint32_t frames = (rate * period_us) / 1000000;
uint32_t frame_bytes = (bcfg->audio_fmt.depth / 8) *
bcfg->audio_fmt.channels_count;

if (frames == 0)
frames = 48;
bcfg->audio_fmt.sampling_frequency = rate;
if (frame_bytes) {
bcfg->ibs = frames * frame_bytes;
if (!cdesc->caps.sink_rate)
bcfg->obs = bcfg->ibs;
}
}
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

again, lets comment phases and assumptions

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done in 80c0fb7 — added inline comments documenting each phase and assumption in the module base_cfg update loop, including upstream fixed-output rate converter propagation via sof_static_resolve_comp_in_rate().

Comment on lines +1972 to +2006
struct sof_sink *sink =
audio_buffer_get_sink(&buf->audio_buffer);
struct sof_source *source =
audio_buffer_get_source(&buf->audio_buffer);
enum sof_ipc_frame fmt = source ?
source_get_frm_fmt(source) : SOF_IPC_FRAME_S16_LE;
uint32_t channels = source ?
source_get_channels(source) : 2;

init_buffer_params(buf, src_cdesc->direction,
audio_stream_get_frm_fmt(&buf->stream),
rate,
audio_stream_get_channels(&buf->stream));
fmt, rate, channels);

struct comp_dev *src_dev =
sof_static_comp_get(route->src_comp_id);
struct comp_dev *sink_dev =
sof_static_comp_get(route->sink_comp_id);

if (sink && src_dev &&
dev_comp_type(src_dev) == SOF_COMP_MODULE_ADAPTER) {
struct processing_module *src_mod =
comp_mod(src_dev);

if (src_mod && src_mod->priv.cfg.base_cfg.obs)
sink_set_min_free_space(sink,
src_mod->priv.cfg.base_cfg.obs);
}
if (source && sink_dev &&
dev_comp_type(sink_dev) == SOF_COMP_MODULE_ADAPTER) {
struct processing_module *sink_mod =
comp_mod(sink_dev);

if (sink_mod && sink_mod->priv.cfg.base_cfg.ibs)
source_set_min_available(source,
sink_mod->priv.cfg.base_cfg.ibs);
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

lets inline comment each phase

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done in 80c0fb7 — added inline comments for each phase of the route buffer update loop (effective rate resolution, PCM parameter refresh, and period threshold recalculation).

In sof_static_topology_init(), Stage 7 calls sof_static_topology_init_controls()
which dispatches to sof_static_kcontrol_set() to set initial default control
values. However, sof_static_kcontrol_set() checks for an active topology via:

    if (!s_active_topo)
        return -ENODEV;

Because s_active_topo was previously assigned only after Stage 7 succeeded,
all initial kcontrol configurations failed with -ENODEV (-19).

Publish s_active_topo prior to running Stage 7 so control lookups succeed,
and ensure s_active_topo is reset to NULL in err_unwind if initialization fails.

Signed-off-by: Liam Girdwood <lgirdwood@gmail.com>
… prepare

Update the static pipeline loader to use the SOF sink/source and
audio_buffer APIs instead of legacy comp_buffer / audio_stream accesses,
and skip unrouted components during Stage 6 prepare:

1. In init_buffer_params(), configure stream parameters, frame/valid
   formats, alignment constants, min_free_space / min_available, and
   buffer reset via sink_set_params(), source_set_params(), and
   audio_buffer_reset().
2. Register instantiated static components in ipc_get()->comp_list so
   IPC4 module bind/unbind callbacks (such as mixout_bind()) can resolve
   connected endpoints via ipc4_get_comp_dev(), with matching list
   cleanup in sof_static_topology_unwind().
3. In sof_static_topology_init_routes() (Stage 4), validate per-component
   source/sink queue indices against CONFIG_MODULE_MAX_CONNECTIONS, IPC4
   queue bitfield limits, and module max_sinks/max_sources before binding
   buffer sof_sink and sof_source interfaces via comp_bind().
   Correspondingly call comp_unbind(), pipeline_disconnect(), and
   audio_buffer_free() in sof_static_topology_unwind().
4. In sof_static_topology_prepare_comps() (Stage 6), check whether
   components have bound sinks/sources via sof_static_comp_has_sink_source()
   (checking mod->num_of_sources / mod->num_of_sinks for module adapter
   components and source_get_comp_state() / sink_get_state() for endpoint
   components) instead of inspecting dev->bsource_list and dev->bsink_list.
5. In sof_static_pipeline_set_sample_rate(), propagate fixed converter
   output rates (caps.sink_rate) downstream via
   sof_static_resolve_comp_in_rate(), query buffer format and channels via
   source_get_frm_fmt() and source_get_channels(), update IPC4 module
   base_cfg rate/IBS/OBS, and refresh sink/source thresholds.

Signed-off-by: Liam Girdwood <lgirdwood@gmail.com>
@lgirdwood
lgirdwood force-pushed the topic/static-pipeline-fixes branch from 6cd4592 to 80c0fb7 Compare November 4, 2026 19:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants