From 483b03a28bf67ce9b1250552248db7b30bd8d56c Mon Sep 17 00:00:00 2001 From: Baoli Zhang Date: Mon, 9 Nov 2026 08:56:04 +0800 Subject: [PATCH 01/12] ASoC: SOF: ipc4-topology: Fix pin format leak in process widget setup sof_ipc4_widget_setup_comp_process() allocates the input and output pin format arrays via sof_ipc4_get_audio_fmt(). When the following sof_ipc4_widget_setup_msg() fails, e.g. because the firmware does not provide a module matching the widget UUID, the error path jumps to the 'err' label, which frees the process struct and clears swidget->private but skips sof_ipc4_free_audio_fmt(). The arrays are then unreachable and are reported by kmemleak: sof_ipc4_get_audio_fmt+0xc9/0x420 [snd_sof] sof_ipc4_widget_setup_comp_process+0x8e/0x200 [snd_sof] snd_sof_load_topology+0x71a/0x840 [snd_sof] sof_pcm_probe+0x77/0x120 [snd_sof] snd_soc_component_probe+0x21/0x60 [snd_soc_core] ... Jump to 'free_available_fmt' instead, as the other process widget error paths after sof_ipc4_get_audio_fmt() already do. The other IPC4 widget setup functions (pga, mixer, src, asrc) free the audio formats in their common error label and are not affected. Fixes: f9efae954905 ("ASoC: SOF: ipc4-topology: Add support for base config extension") Cc: stable@vger.kernel.org Signed-off-by: Baoli Zhang --- sound/soc/sof/ipc4-topology.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index 4e72a1a93d34aa..6825aacec33584 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -1319,7 +1319,7 @@ static int sof_ipc4_widget_setup_comp_process(struct snd_sof_widget *swidget) ret = sof_ipc4_widget_setup_msg(swidget, &process->msg); if (ret) - goto err; + goto free_available_fmt; /* parse process init module payload config type from module info */ fw_module = swidget->module_info; From 82689840bae8493d5be94ce99b77f460b88c79cd Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 23 Oct 2026 16:41:26 +0300 Subject: [PATCH 02/12] ASoC: SOF: topology: Require pin binding for all pins if defined The pin binding array is only stored if the binding of pin 0 is defined in topology. The bindings of the other pins are assumed to be present and they are compared with strcmp() when looking for the pin a widget is connected to, which results in a NULL pointer dereference if the topology does not define the binding for all pins. If the binding of pin 0 is not defined, the parsed bindings of the other pins are leaked. The pin binding must be defined for all pins, with "NotConnected" for the unused pins as documented in struct snd_sof_widget. Reject the topology if the binding is only defined for some of the pins. Fixes: 3b3acedbd0f3 ("ASoC: SOF: Add support to parse pin binding array from topology") Signed-off-by: Peter Ujfalusi --- sound/soc/sof/topology.c | 40 +++++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c index 750916b57def74..cf902e898a9813 100644 --- a/sound/soc/sof/topology.c +++ b/sound/soc/sof/topology.c @@ -1364,6 +1364,7 @@ static int sof_parse_pin_binding(struct snd_sof_widget *swidget, { const struct sof_topology_token *pin_binding_token; char *pin_binding[SOF_WIDGET_MAX_NUM_PINS]; + u32 num_bound = 0; int token_count; u32 num_pins; char **pb; @@ -1387,19 +1388,36 @@ static int sof_parse_pin_binding(struct snd_sof_widget *swidget, if (ret < 0) goto err; - /* copy pin binding array to swidget only if it is defined in topology */ - if (pin_binding[0]) { - pb = kmemdup_array(pin_binding, num_pins, sizeof(char *), GFP_KERNEL); - if (!pb) { - ret = -ENOMEM; - goto err; - } - if (pin_type == SOF_PIN_TYPE_INPUT) - swidget->input_pin_binding = pb; - else - swidget->output_pin_binding = pb; + for (i = 0; i < num_pins; i++) { + if (pin_binding[i]) + num_bound++; } + /* pin binding is optional */ + if (!num_bound) + return 0; + + /* if defined, the pin binding must cover all pins */ + if (num_bound != num_pins) { + dev_err(swidget->scomp->dev, + "%s: Not all %s pins have bindings defined (%u of %u)\n", + swidget->widget->name, + pin_type == SOF_PIN_TYPE_INPUT ? "input" : "output", + num_bound, num_pins); + ret = -EINVAL; + goto err; + } + + pb = kmemdup_array(pin_binding, num_pins, sizeof(char *), GFP_KERNEL); + if (!pb) { + ret = -ENOMEM; + goto err; + } + if (pin_type == SOF_PIN_TYPE_INPUT) + swidget->input_pin_binding = pb; + else + swidget->output_pin_binding = pb; + return 0; err: From 84b27c98b2c11222db3b23b5831cb4176735eef0 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 28 Oct 2026 11:43:52 +0200 Subject: [PATCH 03/12] ASoC: SOF: ipc4-topology: Extract process module output format lookup Move the output format selection of process modules to sof_ipc4_process_select_output_fmt(). Pass the selected input format, or NULL for modules without input formats. Keep the prepared-widget shortcut and pin 0-only parameter propagation unchanged. This prepares for separating module configuration from the output format propagated along each path. No functional change. Signed-off-by: Peter Ujfalusi --- sound/soc/sof/ipc4-topology.c | 137 +++++++++++++++++++--------------- 1 file changed, 76 insertions(+), 61 deletions(-) diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index 6825aacec33584..8efe694497d94a 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -2973,6 +2973,76 @@ static int sof_ipc4_process_add_base_cfg_extn(struct snd_sof_widget *swidget) return 0; } +/* + * Select the output format for the process module's base configuration. Return + * its index in available_fmt->output_pin_fmts, or an error. + * @in_fmt is the selected input format, NULL for modules without input formats. + */ +static int +sof_ipc4_process_select_output_fmt(struct snd_sof_dev *sdev, + struct snd_sof_widget *swidget, + struct sof_ipc4_base_module_cfg *base_config, + struct sof_ipc4_available_audio_format *available_fmt, + struct sof_ipc4_audio_format *in_fmt, + struct snd_pcm_hw_params *fe_params, int dir) +{ + u32 ref_rate, ref_channels; + int ref_valid_bits, ref_type; + + if (in_fmt) { + /* + * The process module can change parameters and their operation + * depends on the direction: + * Playback: typically they have single output format. This is + * to 'force' the conversion from input to output. + * Use the input format as reference since the single + * format is going to be picked. + * Capture: typically they have multiple output formats to + * convert from dai (input) to FE (output) parameters. + * Use the input format as base and replace the param + * which is changed by the module with the FE parameter + * Reason: we can have module which changes the + * parameters in path, we cannot use the full + * FE param set for the module output lookup. + */ + ref_rate = in_fmt->sampling_frequency; + ref_channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(in_fmt->fmt_cfg); + ref_valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(in_fmt->fmt_cfg); + ref_type = sof_ipc4_fmt_cfg_to_type(in_fmt->fmt_cfg); + } else { + /* for modules without input formats, use FE params as reference */ + ref_rate = params_rate(fe_params); + ref_channels = params_channels(fe_params); + ref_type = sof_ipc4_get_sample_type(sdev, fe_params); + if (ref_type < 0) + return ref_type; + + ref_valid_bits = sof_ipc4_get_valid_bits(sdev, fe_params); + if (ref_valid_bits < 0) + return ref_valid_bits; + } + + if (dir == SNDRV_PCM_STREAM_CAPTURE) { + if (available_fmt->changed_params & BIT(SNDRV_PCM_HW_PARAM_RATE)) + ref_rate = params_rate(fe_params); + if (available_fmt->changed_params & BIT(SNDRV_PCM_HW_PARAM_CHANNELS)) + ref_channels = params_channels(fe_params); + if (available_fmt->changed_params & BIT(SNDRV_PCM_HW_PARAM_FORMAT)) { + ref_valid_bits = sof_ipc4_get_valid_bits(sdev, fe_params); + if (ref_valid_bits < 0) + return ref_valid_bits; + + ref_type = sof_ipc4_get_sample_type(sdev, fe_params); + if (ref_type < 0) + return ref_type; + } + } + + return sof_ipc4_init_output_audio_fmt(sdev, swidget, base_config, available_fmt, + ref_rate, ref_channels, ref_valid_bits, + ref_type); +} + static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget, struct snd_pcm_hw_params *fe_params, struct snd_sof_platform_stream_params *platform_params, @@ -3010,71 +3080,16 @@ static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget, /* Configure output audio format only if the module supports output */ if (available_fmt->num_output_formats) { - struct sof_ipc4_audio_format *in_fmt; + struct sof_ipc4_audio_format *in_fmt = NULL; struct sof_ipc4_pin_format *pin_fmt; - u32 ref_rate, ref_channels; - int ref_valid_bits, ref_type; - if (available_fmt->num_input_formats) { - /* - * The process module can change parameters and their operation - * depends on the direction: - * Playback: typically they have single output format. This is - * to 'force' the conversion from input to output. - * Use the input format as reference since the single - * format is going to be picked. - * Capture: typically they have multiple output formats to - * convert from dai (input) to FE (output) parameters. - * Use the input format as base and replace the param - * which is changed by the module with the FE parameter - * Reason: we can have module which changes the - * parameters in path, we cannot use the full - * FE param set for the module output lookup. - */ + if (available_fmt->num_input_formats) in_fmt = &available_fmt->input_pin_fmts[input_fmt_index].audio_fmt; - ref_rate = in_fmt->sampling_frequency; - ref_channels = - SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(in_fmt->fmt_cfg); - ref_valid_bits = - SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(in_fmt->fmt_cfg); - ref_type = sof_ipc4_fmt_cfg_to_type(in_fmt->fmt_cfg); - } else { - /* for modules without input formats, use FE params as reference */ - ref_rate = params_rate(fe_params); - ref_channels = params_channels(fe_params); - ret = sof_ipc4_get_sample_type(sdev, fe_params); - if (ret < 0) - return ret; - ref_type = (u32)ret; - - ref_valid_bits = sof_ipc4_get_valid_bits(sdev, fe_params); - if (ref_valid_bits < 0) - return ref_valid_bits; - } - - if (dir == SNDRV_PCM_STREAM_CAPTURE) { - if (available_fmt->changed_params & BIT(SNDRV_PCM_HW_PARAM_RATE)) - ref_rate = params_rate(fe_params); - if (available_fmt->changed_params & BIT(SNDRV_PCM_HW_PARAM_CHANNELS)) - ref_channels = params_channels(fe_params); - if (available_fmt->changed_params & BIT(SNDRV_PCM_HW_PARAM_FORMAT)) { - ref_valid_bits = sof_ipc4_get_valid_bits(sdev, fe_params); - if (ref_valid_bits < 0) - return ref_valid_bits; - - ref_type = sof_ipc4_get_sample_type(sdev, fe_params); - if (ref_type < 0) - return ref_type; - } - } - output_fmt_index = sof_ipc4_init_output_audio_fmt(sdev, swidget, - &process->base_config, - available_fmt, - ref_rate, - ref_channels, - ref_valid_bits, - ref_type); + output_fmt_index = sof_ipc4_process_select_output_fmt(sdev, swidget, + &process->base_config, + available_fmt, in_fmt, + fe_params, dir); if (output_fmt_index < 0) return output_fmt_index; From 5f09fd3a3436329f46c7906be21bcddaf0e39f26 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Thu, 22 Oct 2026 17:51:07 +0300 Subject: [PATCH 04/12] ASoC: SOF: Pass the output pin feeding the connection to widget ipc_prepare The ipc_prepare op of a widget is called once, before its sinks are walked, without information about which output pin the path continues on. A widget with multiple output pins, each with its own format, cannot tell which pin format to use as the input params of the next widget. The pin cannot be looked up once for the whole PCM walk either: a widget can feed several sinks of the same PCM graph via different pins, like the KPB in the Multi-WOV topology, with its two outputs reconverging at the WOV arbiter. Call ipc_prepare once per outgoing connection in the PCM widget list and pass the output pin feeding that sink: - 0 for widgets with a single output pin, - the pin bound to the sink in topology for widgets with multiple pins, - SOF_OUTPUT_PIN_UNKNOWN if the widget has no output pin bindings, - SOF_OUTPUT_PIN_NONE for widgets without a downstream connection in the PCM, which still receive one call. A connection from a widget without output pins or to a sink missing from the bindings is rejected. Virtual widgets (input, output, out_drv) are not connected in the firmware, the walk does not continue to them and they are not counted as connections. The first call initializes the widget and marks it prepared, subsequent calls only propagate the format of the given pin. When the widget fans out to multiple sinks each branch is walked with its own copy of the incoming params, so that the params propagated along one branch do not leak into the others. The source is unprepared if any of its connections fails. The IPC4 prepare callbacks are updated for the new parameter, the output pin is not used yet. Signed-off-by: Peter Ujfalusi --- sound/soc/sof/ipc4-topology.c | 15 ++- sound/soc/sof/sof-audio.c | 171 ++++++++++++++++++++++++++++------ sound/soc/sof/sof-audio.h | 16 +++- 3 files changed, 165 insertions(+), 37 deletions(-) diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index 8efe694497d94a..249010b4a05bbf 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -2677,7 +2677,8 @@ static int sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, struct snd_pcm_hw_params *fe_params, struct snd_sof_platform_stream_params *platform_params, - struct snd_pcm_hw_params *pipeline_params, int dir) + struct snd_pcm_hw_params *pipeline_params, int dir, + int out_pin) { if (swidget->prepared) return sof_ipc4_copier_module_update_params(swidget, @@ -2691,7 +2692,8 @@ sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, static int sof_ipc4_prepare_gain_module(struct snd_sof_widget *swidget, struct snd_pcm_hw_params *fe_params, struct snd_sof_platform_stream_params *platform_params, - struct snd_pcm_hw_params *pipeline_params, int dir) + struct snd_pcm_hw_params *pipeline_params, int dir, + int out_pin) { struct snd_soc_component *scomp = swidget->scomp; struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); @@ -2740,7 +2742,8 @@ static int sof_ipc4_prepare_gain_module(struct snd_sof_widget *swidget, static int sof_ipc4_prepare_mixer_module(struct snd_sof_widget *swidget, struct snd_pcm_hw_params *fe_params, struct snd_sof_platform_stream_params *platform_params, - struct snd_pcm_hw_params *pipeline_params, int dir) + struct snd_pcm_hw_params *pipeline_params, int dir, + int out_pin) { struct snd_soc_component *scomp = swidget->scomp; struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); @@ -2789,7 +2792,8 @@ static int sof_ipc4_prepare_mixer_module(struct snd_sof_widget *swidget, static int sof_ipc4_prepare_src_module(struct snd_sof_widget *swidget, struct snd_pcm_hw_params *fe_params, struct snd_sof_platform_stream_params *platform_params, - struct snd_pcm_hw_params *pipeline_params, int dir) + struct snd_pcm_hw_params *pipeline_params, int dir, + int out_pin) { struct snd_soc_component *scomp = swidget->scomp; struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); @@ -3046,7 +3050,8 @@ sof_ipc4_process_select_output_fmt(struct snd_sof_dev *sdev, static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget, struct snd_pcm_hw_params *fe_params, struct snd_sof_platform_stream_params *platform_params, - struct snd_pcm_hw_params *pipeline_params, int dir) + struct snd_pcm_hw_params *pipeline_params, int dir, + int out_pin) { struct snd_soc_component *scomp = swidget->scomp; struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); diff --git a/sound/soc/sof/sof-audio.c b/sound/soc/sof/sof-audio.c index 8cbeca471542a6..93f22faaa620c4 100644 --- a/sound/soc/sof/sof-audio.c +++ b/sound/soc/sof/sof-audio.c @@ -9,6 +9,7 @@ // #include +#include #include #include "sof-audio.h" #include "sof-utils.h" @@ -475,6 +476,96 @@ sof_unprepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widg } } +/* + * Set @pin_id to the output pin of @swidget feeding @sink: 0 for widgets with a + * single output pin, the pin bound to @sink in topology otherwise, or + * SOF_OUTPUT_PIN_ANY if bindings are absent. Return 0 or an error. + */ +static int sof_widget_get_output_pin(struct snd_sof_widget *swidget, + struct snd_soc_dapm_widget *sink, int *pin_id) +{ + int i; + + if (!swidget->num_output_pins) { + dev_err(swidget->scomp->dev, "%s: connection to %s without output pins\n", + swidget->widget->name, sink->name); + return -EINVAL; + } + + if (swidget->num_output_pins == 1) { + *pin_id = 0; + return 0; + } + + if (!swidget->output_pin_binding) { + *pin_id = SOF_OUTPUT_PIN_ANY; + return 0; + } + + for (i = 0; i < swidget->num_output_pins; i++) { + if (!strcmp(swidget->output_pin_binding[i], sink->name)) { + *pin_id = i; + return 0; + } + } + + dev_err(swidget->scomp->dev, "%s: no output pin binding for %s\n", + swidget->widget->name, sink->name); + return -EINVAL; +} + +/* + * Prepare @swidget for the connection to @sink, or without a downstream + * connection if @sink is NULL. The first call initializes the widget; later calls + * propagate the configured format of the output pin feeding this connection. + */ +static int sof_prepare_widget_for_sink(struct snd_sof_widget *swidget, + const struct sof_ipc_tplg_widget_ops *widget_ops, + struct snd_soc_dapm_widget *sink, + struct snd_pcm_hw_params *fe_params, + struct snd_sof_platform_stream_params *platform_params, + struct snd_pcm_hw_params *pipeline_params, int dir) +{ + struct snd_soc_dapm_widget *widget; + int out_pin = SOF_OUTPUT_PIN_NONE; + int ret; + + if (!swidget || !widget_ops) + return 0; + + widget = swidget->widget; + if (!widget_ops[widget->id].ipc_prepare || is_aggregated_dai(swidget)) + return 0; + + if (sink) { + ret = sof_widget_get_output_pin(swidget, sink, &out_pin); + if (ret < 0) + return ret; + } + + ret = widget_ops[widget->id].ipc_prepare(swidget, fe_params, platform_params, + pipeline_params, dir, out_pin); + if (ret < 0) { + dev_err(swidget->scomp->dev, "failed to prepare widget %s\n", widget->name); + return ret; + } + + swidget->prepared = true; + return 0; +} + +/* + * Check if the prepare walk continues to the sink of @p: a SOF widget of the PCM + * which is not walked already. Virtual widgets are not connected in the firmware, + * the walk does not continue to them. + */ +static bool sof_prepare_walks_to_sink(struct snd_sof_dev *sdev, struct snd_soc_dapm_path *p, + struct snd_soc_dapm_widget_list *list) +{ + return widget_in_list(list, p->sink) && !p->walking && p->sink->dobj.private && + !is_virtual_widget(sdev, p->sink, __func__); +} + static int sof_prepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *widget, struct snd_pcm_hw_params *fe_params, @@ -484,8 +575,10 @@ sof_prepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget { const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg); struct snd_sof_widget *swidget = widget->dobj.private; - const struct sof_ipc_tplg_widget_ops *widget_ops; + const struct sof_ipc_tplg_widget_ops *widget_ops = NULL; + struct snd_pcm_hw_params *branch_params __free(kfree) = NULL; struct snd_soc_dapm_path *p; + int num_sinks = 0; int ret; if (is_virtual_widget(sdev, widget, __func__)) @@ -502,45 +595,63 @@ sof_prepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget !sof_widget_in_same_direction(swidget, dir)) return 0; - /* skip widgets aggregated DAI widgets */ - if (!widget_ops[widget->id].ipc_prepare || is_aggregated_dai(swidget)) - goto sink_prepare; +sink_prepare: + /* + * Prepare and propagate for each connection. When the widget fans out to + * multiple sinks each branch must start from the source's incoming params, + * not the params modified by a previous branch. + * + * Stop at the second sink: only fanout needs separate branch params. + */ + snd_soc_dapm_widget_for_each_sink_path(widget, p) { + if (sof_prepare_walks_to_sink(sdev, p, list) && ++num_sinks > 1) { + branch_params = kmalloc_obj(*branch_params); + if (!branch_params) + return -ENOMEM; - /* prepare the source widget */ - ret = widget_ops[widget->id].ipc_prepare(swidget, fe_params, platform_params, - pipeline_params, dir); - if (ret < 0) { - dev_err(sdev->dev, "failed to prepare widget %s\n", widget->name); - return ret; + break; + } } - swidget->prepared = true; - -sink_prepare: - /* prepare all widgets in the sink paths */ snd_soc_dapm_widget_for_each_sink_path(widget, p) { - if (!widget_in_list(list, p->sink)) + struct snd_pcm_hw_params *params = pipeline_params; + + if (!sof_prepare_walks_to_sink(sdev, p, list)) continue; - if (!p->walking && p->sink->dobj.private) { - p->walking = true; - ret = sof_prepare_widgets_in_path(sdev, p->sink, fe_params, - platform_params, pipeline_params, dir, - list); - p->walking = false; - if (ret < 0) { - /* unprepare the source widget */ - if (widget_ops[widget->id].ipc_unprepare && - swidget && swidget->prepared && swidget->use_count == 0) { - widget_ops[widget->id].ipc_unprepare(swidget); - swidget->prepared = false; - } - return ret; - } + if (branch_params) { + *branch_params = *pipeline_params; + params = branch_params; } + + ret = sof_prepare_widget_for_sink(swidget, widget_ops, p->sink, + fe_params, platform_params, + params, dir); + if (ret < 0) + goto unprepare; + + p->walking = true; + ret = sof_prepare_widgets_in_path(sdev, p->sink, fe_params, + platform_params, params, dir, + list); + p->walking = false; + if (ret < 0) + goto unprepare; } + if (!num_sinks) + return sof_prepare_widget_for_sink(swidget, widget_ops, NULL, fe_params, + platform_params, pipeline_params, dir); + return 0; + +unprepare: + if (widget_ops && widget_ops[widget->id].ipc_unprepare && + swidget && swidget->prepared && swidget->use_count == 0) { + widget_ops[widget->id].ipc_unprepare(swidget); + swidget->prepared = false; + } + return ret; } /* diff --git a/sound/soc/sof/sof-audio.h b/sound/soc/sof/sof-audio.h index 10b41ab18b7990..d1a12246fe4c46 100644 --- a/sound/soc/sof/sof-audio.h +++ b/sound/soc/sof/sof-audio.h @@ -172,6 +172,11 @@ struct sof_ipc_tplg_control_ops { int size); }; +enum sof_widget_output_pin { + SOF_OUTPUT_PIN_ANY = -1, + SOF_OUTPUT_PIN_NONE = -2, +}; + /** * struct sof_ipc_tplg_widget_ops - IPC-specific ops for topology widgets * @ipc_setup: Function pointer for setting up widget IPC params @@ -179,7 +184,13 @@ struct sof_ipc_tplg_control_ops { * @token_list: List of token ID's that should be parsed for the widget * @token_list_size: number of elements in token_list * @bind_event: Function pointer for binding events to the widget - * @ipc_prepare: Optional op for preparing a widget for set up + * @ipc_prepare: Optional op for preparing a widget for set up. Called for each + * outgoing connection with a separate copy of the input params. + * Only the first call initializes the widget; subsequent calls + * propagate its configured output format. @out_pin is the + * output pin of the widget feeding the connection being prepared, + * SOF_OUTPUT_PIN_ANY if bindings are absent, or + * SOF_OUTPUT_PIN_NONE if there is no downstream connection * @ipc_unprepare: Optional op for unpreparing a widget */ struct sof_ipc_tplg_widget_ops { @@ -192,7 +203,8 @@ struct sof_ipc_tplg_widget_ops { int (*ipc_prepare)(struct snd_sof_widget *swidget, struct snd_pcm_hw_params *fe_params, struct snd_sof_platform_stream_params *platform_params, - struct snd_pcm_hw_params *source_params, int dir); + struct snd_pcm_hw_params *source_params, int dir, + int out_pin); void (*ipc_unprepare)(struct snd_sof_widget *swidget); }; From f9178d5c73cf49ae799e25a56e61c7ce03a16b00 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 23 Oct 2026 15:37:51 +0300 Subject: [PATCH 05/12] ASoC: SOF: ipc4-topology: Support output format lookup on a single pin The output format lookup selects among the formats of all output pins of a module. Modules with multiple output pins need to select the format among the formats of a given pin. Add a pin parameter to sof_ipc4_init_output_audio_fmt() and sof_ipc4_is_single_format() to only consider the formats of that pin. SOF_OUTPUT_PIN_ANY considers the formats of all pins as before. Also allow selecting an output format without updating the obs of a base config, for pins which are not configured via the base config. Factor out the format comparison to sof_ipc4_is_same_fmt() and move the pin format helpers next to sof_ipc4_fmt_cfg_to_type(), so that they can also be used while the topology is parsed. All callers pass SOF_OUTPUT_PIN_ANY, no functional change. Signed-off-by: Peter Ujfalusi --- sound/soc/sof/ipc4-topology.c | 126 ++++++++++++++++++++++------------ 1 file changed, 84 insertions(+), 42 deletions(-) diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index 249010b4a05bbf..79626153f10821 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -255,6 +255,60 @@ static u32 sof_ipc4_fmt_cfg_to_type(u32 fmt_cfg) return SOF_IPC4_TYPE_LSB_INTEGER; } +/* Check if @a and @b have the same rate, channel count and valid bit depth */ +static bool sof_ipc4_is_same_fmt(const struct sof_ipc4_audio_format *a, + const struct sof_ipc4_audio_format *b) +{ + return a->sampling_frequency == b->sampling_frequency && + SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(a->fmt_cfg) == + SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(b->fmt_cfg) && + SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(a->fmt_cfg) == + SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(b->fmt_cfg); +} + +/* Check if @pin_fmt belongs to @pin, SOF_OUTPUT_PIN_ANY matches all pins */ +static bool sof_ipc4_is_pin_fmt(struct sof_ipc4_pin_format *pin_fmt, int pin) +{ + return pin < 0 || pin_fmt->pin_index == pin; +} + +/* Return the index of the first format of @pin in @pin_fmts, -1 if there is none */ +static int sof_ipc4_get_first_pin_fmt(struct sof_ipc4_pin_format *pin_fmts, + u32 pin_fmts_size, int pin) +{ + int i; + + for (i = 0; i < pin_fmts_size; i++) { + if (sof_ipc4_is_pin_fmt(&pin_fmts[i], pin)) + return i; + } + + return -1; +} + +/* + * Check if all formats of @pin are the same, SOF_OUTPUT_PIN_ANY checks all formats. + * False if there are no formats for @pin. + */ +static bool sof_ipc4_is_single_format(struct snd_sof_dev *sdev, + struct sof_ipc4_pin_format *pin_fmts, u32 pin_fmts_size, + int pin) +{ + int first, i; + + first = sof_ipc4_get_first_pin_fmt(pin_fmts, pin_fmts_size, pin); + if (first < 0) + return false; + + for (i = first + 1; i < pin_fmts_size; i++) { + if (sof_ipc4_is_pin_fmt(&pin_fmts[i], pin) && + !sof_ipc4_is_same_fmt(&pin_fmts[first].audio_fmt, &pin_fmts[i].audio_fmt)) + return false; + } + + return true; +} + static void sof_ipc4_dbg_audio_format(struct device *dev, struct sof_ipc4_pin_format *pin_fmt, int num_formats) { @@ -1545,53 +1599,35 @@ static int sof_ipc4_update_hw_params(struct snd_sof_dev *sdev, struct snd_pcm_hw return 0; } -static bool sof_ipc4_is_single_format(struct snd_sof_dev *sdev, - struct sof_ipc4_pin_format *pin_fmts, u32 pin_fmts_size) -{ - struct sof_ipc4_audio_format *fmt; - u32 rate, channels, valid_bits; - int i; - - fmt = &pin_fmts[0].audio_fmt; - rate = fmt->sampling_frequency; - channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(fmt->fmt_cfg); - valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(fmt->fmt_cfg); - - /* check if all output formats in topology are the same */ - for (i = 1; i < pin_fmts_size; i++) { - u32 _rate, _channels, _valid_bits; - - fmt = &pin_fmts[i].audio_fmt; - _rate = fmt->sampling_frequency; - _channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(fmt->fmt_cfg); - _valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(fmt->fmt_cfg); - - if (_rate != rate || _channels != channels || _valid_bits != valid_bits) - return false; - } - - return true; -} - +/* + * Select the output format of @pin, a negative @pin selects among the formats of all + * pins: the first format if all formats are the same, the one matching the reference + * params otherwise. The obs of @base_config is updated if it is not NULL. + */ static int sof_ipc4_init_output_audio_fmt(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget, struct sof_ipc4_base_module_cfg *base_config, struct sof_ipc4_available_audio_format *available_fmt, - u32 out_ref_rate, u32 out_ref_channels, + int pin, u32 out_ref_rate, u32 out_ref_channels, u32 out_ref_valid_bits, u32 out_ref_type) { struct sof_ipc4_pin_format *pin_fmts = available_fmt->output_pin_fmts; u32 pin_fmts_size = available_fmt->num_output_formats; bool single_format; - int i = 0; + int i; - if (!pin_fmts_size) { - dev_err(sdev->dev, "no output formats for %s\n", - swidget->widget->name); + i = sof_ipc4_get_first_pin_fmt(pin_fmts, pin_fmts_size, pin); + if (i < 0) { + if (pin < 0) + dev_err(sdev->dev, "no output formats for %s\n", + swidget->widget->name); + else + dev_err(sdev->dev, "no output formats on pin %d for %s\n", + pin, swidget->widget->name); return -EINVAL; } - single_format = sof_ipc4_is_single_format(sdev, pin_fmts, pin_fmts_size); + single_format = sof_ipc4_is_single_format(sdev, pin_fmts, pin_fmts_size, pin); /* pick the first format if there's only one available or if all formats are the same */ if (single_format) @@ -1606,6 +1642,9 @@ static int sof_ipc4_init_output_audio_fmt(struct snd_sof_dev *sdev, u32 _out_rate, _out_channels, _out_valid_bits, _out_type; + if (!sof_ipc4_is_pin_fmt(&pin_fmts[i], pin)) + continue; + _out_rate = fmt->sampling_frequency; _out_channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(fmt->fmt_cfg); _out_valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(fmt->fmt_cfg); @@ -1624,7 +1663,8 @@ static int sof_ipc4_init_output_audio_fmt(struct snd_sof_dev *sdev, return -EINVAL; out_fmt: - base_config->obs = pin_fmts[i].buffer_size; + if (base_config) + base_config->obs = pin_fmts[i].buffer_size; return i; } @@ -1694,7 +1734,8 @@ static int sof_ipc4_init_input_audio_fmt(struct snd_sof_dev *sdev, return -EINVAL; } - single_format = sof_ipc4_is_single_format(sdev, pin_fmts, pin_fmts_size); + single_format = sof_ipc4_is_single_format(sdev, pin_fmts, pin_fmts_size, + SOF_OUTPUT_PIN_ANY); if (single_format) goto in_fmt; @@ -2431,7 +2472,8 @@ _sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, output_fmt_index = sof_ipc4_init_output_audio_fmt(sdev, swidget, &copier_data->base_config, - available_fmt, out_ref_rate, + available_fmt, SOF_OUTPUT_PIN_ANY, + out_ref_rate, out_ref_channels, out_ref_valid_bits, out_ref_type); if (output_fmt_index < 0) @@ -2722,7 +2764,7 @@ static int sof_ipc4_prepare_gain_module(struct snd_sof_widget *swidget, output_fmt_index = sof_ipc4_init_output_audio_fmt(sdev, swidget, &gain->data.base_config, - available_fmt, + available_fmt, SOF_OUTPUT_PIN_ANY, out_ref_rate, out_ref_channels, out_ref_valid_bits, @@ -2772,7 +2814,7 @@ static int sof_ipc4_prepare_mixer_module(struct snd_sof_widget *swidget, output_fmt_index = sof_ipc4_init_output_audio_fmt(sdev, swidget, &mixer->base_config, - available_fmt, + available_fmt, SOF_OUTPUT_PIN_ANY, out_ref_rate, out_ref_channels, out_ref_valid_bits, @@ -2875,7 +2917,7 @@ static int sof_ipc4_prepare_src_module(struct snd_sof_widget *swidget, output_fmt_index = sof_ipc4_init_output_audio_fmt(sdev, swidget, base_config, - available_fmt, + available_fmt, SOF_OUTPUT_PIN_ANY, out_ref_rate, out_ref_channels, out_ref_valid_bits, @@ -3043,8 +3085,8 @@ sof_ipc4_process_select_output_fmt(struct snd_sof_dev *sdev, } return sof_ipc4_init_output_audio_fmt(sdev, swidget, base_config, available_fmt, - ref_rate, ref_channels, ref_valid_bits, - ref_type); + SOF_OUTPUT_PIN_ANY, ref_rate, ref_channels, + ref_valid_bits, ref_type); } static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget, From 4b4594a2bf71dd8a4c6080af070d843b99297175 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Thu, 29 Oct 2026 10:23:41 +0200 Subject: [PATCH 06/12] ASoC: SOF: ipc4-topology: Compare the sample type when checking for a single format The formats of a pin are treated as a single format if they have the same rate, channel count and valid bit depth. In that case the first format is used without matching the formats against the params. Formats which only differ in their sample type, like 32-bit integer and 32-bit float, or unsigned 8-bit and A-law/mu-law, are not the same format: the first one is used even if the params ask for the other one. Compare the sample type as well, the same way as the formats are matched against the params. Signed-off-by: Peter Ujfalusi --- sound/soc/sof/ipc4-topology.c | 43 +++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index 79626153f10821..3cb6196472a74f 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -238,13 +238,18 @@ struct snd_sof_widget *sof_ipc4_find_swidget_by_ids(struct snd_sof_dev *sdev, return NULL; } -static u32 sof_ipc4_fmt_cfg_to_type(u32 fmt_cfg) +static u32 sof_ipc4_fmt_to_type(const struct sof_ipc4_audio_format *fmt) { - /* Fetch the sample type from the fmt for 8 and 32 bit formats */ - u32 __bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(fmt_cfg); + u32 valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(fmt->fmt_cfg); + u32 type = SOF_IPC4_AUDIO_FORMAT_CFG_SAMPLE_TYPE(fmt->fmt_cfg); - if (__bits == 8 || __bits == 32) - return SOF_IPC4_AUDIO_FORMAT_CFG_SAMPLE_TYPE(fmt_cfg); + /* MSB and LSB alignment coincide when a 32-bit sample fills its container. */ + if (valid_bits == 32 && fmt->bit_depth == 32 && + type == SOF_IPC4_TYPE_MSB_INTEGER) + return SOF_IPC4_TYPE_LSB_INTEGER; + + if (valid_bits == 8 || valid_bits == 32) + return type; /* * Return LSB integer type for 16, 20 and 24 formats as the firmware is @@ -255,7 +260,10 @@ static u32 sof_ipc4_fmt_cfg_to_type(u32 fmt_cfg) return SOF_IPC4_TYPE_LSB_INTEGER; } -/* Check if @a and @b have the same rate, channel count and valid bit depth */ +/* + * Check if @a and @b are the same format: the rate, channel count, valid bit depth + * and sample type are compared, like when a format is matched against the params. + */ static bool sof_ipc4_is_same_fmt(const struct sof_ipc4_audio_format *a, const struct sof_ipc4_audio_format *b) { @@ -263,7 +271,8 @@ static bool sof_ipc4_is_same_fmt(const struct sof_ipc4_audio_format *a, SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(a->fmt_cfg) == SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(b->fmt_cfg) && SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(a->fmt_cfg) == - SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(b->fmt_cfg); + SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(b->fmt_cfg) && + sof_ipc4_fmt_to_type(a) == sof_ipc4_fmt_to_type(b); } /* Check if @pin_fmt belongs to @pin, SOF_OUTPUT_PIN_ANY matches all pins */ @@ -319,7 +328,7 @@ static void sof_ipc4_dbg_audio_format(struct device *dev, struct sof_ipc4_pin_fo dev_dbg(dev, "Pin #%d: %uHz, %ubit (type: %u), %luch (ch_map %#x ch_cfg %u interleaving_style %u fmt_cfg %#x) buffer size %d\n", pin_fmt[i].pin_index, fmt->sampling_frequency, fmt->bit_depth, - sof_ipc4_fmt_cfg_to_type(fmt->fmt_cfg), + sof_ipc4_fmt_to_type(fmt), SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(fmt->fmt_cfg), fmt->ch_map, fmt->ch_cfg, fmt->interleaving_style, fmt->fmt_cfg, pin_fmt[i].buffer_size); @@ -1530,7 +1539,7 @@ static int sof_ipc4_update_hw_params(struct snd_sof_dev *sdev, struct snd_pcm_hw if (param_to_update & BIT(SNDRV_PCM_HW_PARAM_FORMAT)) { int valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(fmt->fmt_cfg); - int type = sof_ipc4_fmt_cfg_to_type(fmt->fmt_cfg); + int type = sof_ipc4_fmt_to_type(fmt); snd_pcm_format_t snd_fmt; struct snd_mask *m; @@ -1648,7 +1657,7 @@ static int sof_ipc4_init_output_audio_fmt(struct snd_sof_dev *sdev, _out_rate = fmt->sampling_frequency; _out_channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(fmt->fmt_cfg); _out_valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(fmt->fmt_cfg); - _out_type = sof_ipc4_fmt_cfg_to_type(fmt->fmt_cfg); + _out_type = sof_ipc4_fmt_to_type(fmt); if (_out_rate == out_ref_rate && _out_channels == out_ref_channels && _out_valid_bits == out_ref_valid_bits && _out_type == out_ref_type) @@ -1760,7 +1769,7 @@ static int sof_ipc4_init_input_audio_fmt(struct snd_sof_dev *sdev, rate = fmt->sampling_frequency; channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(fmt->fmt_cfg); valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(fmt->fmt_cfg); - type = sof_ipc4_fmt_cfg_to_type(fmt->fmt_cfg); + type = sof_ipc4_fmt_to_type(fmt); if (params_rate(params) == rate && params_channels(params) == channels && sample_valid_bits == valid_bits && sample_type == type) break; @@ -2427,7 +2436,7 @@ _sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, in_fmt = &available_fmt->input_pin_fmts[input_fmt_index].audio_fmt; out_ref_rate = in_fmt->sampling_frequency; out_ref_channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(in_fmt->fmt_cfg); - out_ref_type = sof_ipc4_fmt_cfg_to_type(in_fmt->fmt_cfg); + out_ref_type = sof_ipc4_fmt_to_type(in_fmt); if (!single_output_bitdepth) out_ref_valid_bits = @@ -2467,7 +2476,7 @@ _sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, out_fmt = &available_fmt->output_pin_fmts[0].audio_fmt; out_ref_valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(out_fmt->fmt_cfg); - out_ref_type = sof_ipc4_fmt_cfg_to_type(out_fmt->fmt_cfg); + out_ref_type = sof_ipc4_fmt_to_type(out_fmt); } output_fmt_index = sof_ipc4_init_output_audio_fmt(sdev, swidget, @@ -2760,7 +2769,7 @@ static int sof_ipc4_prepare_gain_module(struct snd_sof_widget *swidget, out_ref_rate = in_fmt->sampling_frequency; out_ref_channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(in_fmt->fmt_cfg); out_ref_valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(in_fmt->fmt_cfg); - out_ref_type = sof_ipc4_fmt_cfg_to_type(in_fmt->fmt_cfg); + out_ref_type = sof_ipc4_fmt_to_type(in_fmt); output_fmt_index = sof_ipc4_init_output_audio_fmt(sdev, swidget, &gain->data.base_config, @@ -2810,7 +2819,7 @@ static int sof_ipc4_prepare_mixer_module(struct snd_sof_widget *swidget, out_ref_rate = in_fmt->sampling_frequency; out_ref_channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(in_fmt->fmt_cfg); out_ref_valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(in_fmt->fmt_cfg); - out_ref_type = sof_ipc4_fmt_cfg_to_type(in_fmt->fmt_cfg); + out_ref_type = sof_ipc4_fmt_to_type(in_fmt); output_fmt_index = sof_ipc4_init_output_audio_fmt(sdev, swidget, &mixer->base_config, @@ -2882,7 +2891,7 @@ static int sof_ipc4_prepare_src_module(struct snd_sof_widget *swidget, in_audio_fmt = &available_fmt->input_pin_fmts[input_fmt_index].audio_fmt; out_ref_channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(in_audio_fmt->fmt_cfg); out_ref_valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(in_audio_fmt->fmt_cfg); - out_ref_type = sof_ipc4_fmt_cfg_to_type(in_audio_fmt->fmt_cfg); + out_ref_type = sof_ipc4_fmt_to_type(in_audio_fmt); if (sink_rate_from_tplg) { /* Use the sink rate as reference */ @@ -3054,7 +3063,7 @@ sof_ipc4_process_select_output_fmt(struct snd_sof_dev *sdev, ref_rate = in_fmt->sampling_frequency; ref_channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(in_fmt->fmt_cfg); ref_valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(in_fmt->fmt_cfg); - ref_type = sof_ipc4_fmt_cfg_to_type(in_fmt->fmt_cfg); + ref_type = sof_ipc4_fmt_to_type(in_fmt); } else { /* for modules without input formats, use FE params as reference */ ref_rate = params_rate(fe_params); From 4ca09ea87f80afa3a3275af9da880a243f70a5a6 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 23 Oct 2026 16:18:10 +0300 Subject: [PATCH 07/12] ASoC: SOF: ipc4-topology: Warn about ambiguous process module pin formats The formats of the pins of a process module are fixed when the module is initialized: pin 0 via the base config, the other pins via the base config extension or by the module itself. Unlike the copier, which can change the format of its output pins with SET_SINK_FORMAT, there is no message to change them afterwards. On a side with multiple pins the module can be initialized via a path using any of the pins. The format of the other pins can only be known if they have a single format, there are no params to select one of their formats from. Warn at topology load if a pin on a side with multiple pins has more than one distinct format. Signed-off-by: Peter Ujfalusi --- sound/soc/sof/ipc4-topology.c | 37 +++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index 3cb6196472a74f..eb8da04d272b46 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -1357,6 +1357,36 @@ static void sof_ipc4_widget_free_comp_mixer(struct snd_sof_widget *swidget) swidget->private = NULL; } +/* + * The formats of the pins of a module are fixed when the module is initialized, + * there is no message to change them afterwards. On a side with multiple pins the + * module can be initialized via a path using any of the pins, the formats of the + * other pins can only be known if they have a single format. + */ +static void sof_ipc4_process_check_pin_formats(struct snd_sof_widget *swidget, + struct sof_ipc4_pin_format *pin_fmts, + u32 num_fmts, int num_pins, bool output) +{ + struct snd_soc_component *scomp = swidget->scomp; + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + int pin; + + if (num_pins <= 1) + return; + + for (pin = 0; pin < num_pins; pin++) { + /* nothing to check for pins without formats in topology */ + if (sof_ipc4_get_first_pin_fmt(pin_fmts, num_fmts, pin) < 0 || + sof_ipc4_is_single_format(sdev, pin_fmts, num_fmts, pin)) + continue; + + dev_warn(scomp->dev, + "%s: %s pin %d has multiple formats, a single format is expected with %d %s pins\n", + swidget->widget->name, output ? "output" : "input", pin, + num_pins, output ? "output" : "input"); + } +} + /* * Add the process modules support. The process modules are defined as snd_soc_dapm_effect modules. */ @@ -1380,6 +1410,13 @@ static int sof_ipc4_widget_setup_comp_process(struct snd_sof_widget *swidget) if (ret) goto err; + sof_ipc4_process_check_pin_formats(swidget, process->available_fmt.input_pin_fmts, + process->available_fmt.num_input_formats, + swidget->num_input_pins, false); + sof_ipc4_process_check_pin_formats(swidget, process->available_fmt.output_pin_fmts, + process->available_fmt.num_output_formats, + swidget->num_output_pins, true); + ret = sof_ipc4_widget_setup_msg(swidget, &process->msg); if (ret) goto free_available_fmt; From 6bb146b7a415a5f7dcf0a4593e8c8f43f97c1fbf Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 28 Oct 2026 11:44:48 +0200 Subject: [PATCH 08/12] ASoC: SOF: ipc4-topology: Use the configured output pin formats of process modules A process module with multiple output pins can be shared by paths of several PCMs. Selecting among all output formats with the FE params guesses the pin used by the path. The prepared-widget shortcut always propagates process->output_format, which describes pin 0 and may not have been set if the first path selected another pin. The module is configured with the output pin 0 format and buffer size in the base config. Other pins use their topology format via the base config extension or are configured by the module itself. Unlike a copier, a process module cannot change pin formats after initialization. Use the output pin feeding the path to separate module configuration from downstream propagation. Select the base config format only among pin 0 formats. If the path leaves on another pin, pin 0 must have a single format since the path params cannot select it. Propagate the configured format of the pin feeding the path on first and subsequent preparation. Pins without topology formats use the pin 0 format. Reject ambiguous secondary pin formats, including when building the base config extension. Modules with input formats are configured once, the pin formats are not selected again with the FE params of a subsequent path. Without output pin bindings the output pin of a sink is only allocated at bind time and the pin 0 format is propagated to it. Accept this only if all output pins are configured with the pin 0 format: pins without formats in topology use it, the others must have it as their single format. When there is no downstream connection, configure pin 0 without propagating an output format. Zero-output consumers skip output format selection entirely. Signed-off-by: Peter Ujfalusi --- sound/soc/sof/ipc4-topology.c | 201 +++++++++++++++++++++++++++------- 1 file changed, 164 insertions(+), 37 deletions(-) diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index eb8da04d272b46..c43ba3afa5160a 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -455,10 +455,7 @@ sof_ipc4_get_input_pin_audio_fmt(struct snd_sof_widget *swidget, int pin_index) base_cfg_ext = process->base_config_ext; - /* - * If there are multiple input formats available for a pin, the first available format - * is chosen. - */ + /* the base config extension holds the format each input pin is configured with */ for (i = 0; i < base_cfg_ext->num_input_pin_fmts; i++) { struct sof_ipc4_pin_format *pin_format = &base_cfg_ext->pin_formats[i]; @@ -2995,6 +2992,7 @@ sof_ipc4_process_set_pin_formats(struct snd_sof_widget *swidget, int pin_type) struct sof_ipc4_available_audio_format *available_fmt = &process->available_fmt; struct sof_ipc4_pin_format *pin_format, *format_list_to_search; struct snd_soc_component *scomp = swidget->scomp; + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); int num_pins, format_list_count; int pin_format_offset = 0; int i, j; @@ -3027,25 +3025,28 @@ sof_ipc4_process_set_pin_formats(struct snd_sof_widget *swidget, int pin_type) } /* - * For all other pins, find the pin formats from those set in topology. If there - * is more than one format specified for a pin, this will pick the first available - * one. + * For all other pins, use the format set in topology. The pin can only + * have a single format, there are no params to select one of multiple + * formats with. */ - for (j = 0; j < format_list_count; j++) { - struct sof_ipc4_pin_format *pin_format_item = &format_list_to_search[j]; - - if (pin_format_item->pin_index == i - pin_format_offset) { - *pin_format = *pin_format_item; - break; - } + j = sof_ipc4_get_first_pin_fmt(format_list_to_search, format_list_count, + i - pin_format_offset); + if (j < 0) { + dev_err(scomp->dev, "%s pin %d format not found for %s\n", + (pin_type == SOF_PIN_TYPE_INPUT) ? "input" : "output", + i - pin_format_offset, swidget->widget->name); + return -EINVAL; } - if (j == format_list_count) { - dev_err(scomp->dev, "%s pin %d format not found for %s\n", + if (!sof_ipc4_is_single_format(sdev, format_list_to_search, format_list_count, + i - pin_format_offset)) { + dev_err(scomp->dev, "%s pin %d format of %s is ambiguous with multiple formats\n", (pin_type == SOF_PIN_TYPE_INPUT) ? "input" : "output", i - pin_format_offset, swidget->widget->name); return -EINVAL; } + + *pin_format = format_list_to_search[j]; } return 0; @@ -3069,6 +3070,13 @@ static int sof_ipc4_process_add_base_cfg_extn(struct snd_sof_widget *swidget) * Select the output format for the process module's base configuration. Return * its index in available_fmt->output_pin_fmts, or an error. * @in_fmt is the selected input format, NULL for modules without input formats. + * @out_pin is the output pin feeding the connection being prepared, normalized + * to pin 0 if there is no downstream connection or output pin bindings. + * + * Modules with multiple output pins are configured with the output pin 0 format, + * which is selected only among the pin 0 formats. The params of the path can only + * be used to select it if the path leaves the module on pin 0, otherwise pin 0 + * must have a single format. */ static int sof_ipc4_process_select_output_fmt(struct snd_sof_dev *sdev, @@ -3076,10 +3084,26 @@ sof_ipc4_process_select_output_fmt(struct snd_sof_dev *sdev, struct sof_ipc4_base_module_cfg *base_config, struct sof_ipc4_available_audio_format *available_fmt, struct sof_ipc4_audio_format *in_fmt, - struct snd_pcm_hw_params *fe_params, int dir) + struct snd_pcm_hw_params *fe_params, int dir, + int out_pin) { + struct sof_ipc4_pin_format *pin_fmts = available_fmt->output_pin_fmts; + u32 num_fmts = available_fmt->num_output_formats; u32 ref_rate, ref_channels; int ref_valid_bits, ref_type; + int pin = SOF_OUTPUT_PIN_ANY; + + if (swidget->num_output_pins > 1) { + pin = 0; + + if (out_pin > 0 && sof_ipc4_get_first_pin_fmt(pin_fmts, num_fmts, 0) >= 0 && + !sof_ipc4_is_single_format(sdev, pin_fmts, num_fmts, 0)) { + dev_err(sdev->dev, + "%s: output pin 0 format is ambiguous, path leaves on pin %d\n", + swidget->widget->name, out_pin); + return -EINVAL; + } + } if (in_fmt) { /* @@ -3131,8 +3155,69 @@ sof_ipc4_process_select_output_fmt(struct snd_sof_dev *sdev, } return sof_ipc4_init_output_audio_fmt(sdev, swidget, base_config, available_fmt, - SOF_OUTPUT_PIN_ANY, ref_rate, ref_channels, - ref_valid_bits, ref_type); + pin, ref_rate, ref_channels, ref_valid_bits, + ref_type); +} + +/* + * Return the format output pin @pin of a process module is configured with: the + * format selected for pin 0, the single topology format of other pins or the pin 0 + * format for pins without formats in topology. NULL if @pin has multiple formats. + */ +static struct sof_ipc4_audio_format * +sof_ipc4_process_get_output_pin_fmt(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget, + int pin) +{ + struct sof_ipc4_process *process = swidget->private; + struct sof_ipc4_available_audio_format *available_fmt = &process->available_fmt; + struct sof_ipc4_pin_format *pin_fmts = available_fmt->output_pin_fmts; + u32 num_fmts = available_fmt->num_output_formats; + int i; + + if (pin <= 0) + return &process->output_format; + + i = sof_ipc4_get_first_pin_fmt(pin_fmts, num_fmts, pin); + if (i < 0) + return &process->output_format; + + if (!sof_ipc4_is_single_format(sdev, pin_fmts, num_fmts, pin)) { + dev_err(sdev->dev, "%s: output pin %d format is ambiguous with multiple formats\n", + swidget->widget->name, pin); + return NULL; + } + + return &pin_fmts[i].audio_fmt; +} + +/* + * Without output pin bindings the output pin a sink is connected to is only + * allocated at bind time and the format of output pin 0 is propagated to the sink. + * The output pin formats of a process module are fixed at init, this is only + * correct if all output pins are configured with the pin 0 format. + */ +static int sof_ipc4_process_check_unbound_output_pins(struct snd_sof_dev *sdev, + struct snd_sof_widget *swidget) +{ + struct sof_ipc4_process *process = swidget->private; + int pin; + + for (pin = 1; pin < swidget->num_output_pins; pin++) { + struct sof_ipc4_audio_format *fmt; + + fmt = sof_ipc4_process_get_output_pin_fmt(sdev, swidget, pin); + if (!fmt) + return -EINVAL; + + if (!sof_ipc4_is_same_fmt(fmt, &process->output_format)) { + dev_err(sdev->dev, + "%s: output pin %d format differs from pin 0, output pin bindings are required\n", + swidget->widget->name, pin); + return -EINVAL; + } + } + + return 0; } static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget, @@ -3145,24 +3230,43 @@ static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget, struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct sof_ipc4_process *process = swidget->private; struct sof_ipc4_available_audio_format *available_fmt = &process->available_fmt; + bool propagate_output = out_pin != SOF_OUTPUT_PIN_NONE; + bool unbound_output = out_pin == SOF_OUTPUT_PIN_ANY; void *cfg = process->ipc_config_data; int output_fmt_index = 0; int input_fmt_index = 0; int ret; - if (available_fmt->num_input_formats) { - if (swidget->prepared) { - if (!available_fmt->num_output_formats) - return 0; - - /* modify the pipeline params with the output format */ - return sof_ipc4_update_hw_params(sdev, pipeline_params, - &process->output_format, - BIT(SNDRV_PCM_HW_PARAM_FORMAT) | - BIT(SNDRV_PCM_HW_PARAM_CHANNELS) | - BIT(SNDRV_PCM_HW_PARAM_RATE)); + /* Without a downstream connection or output pin bindings use output pin 0 */ + if (out_pin < 0) + out_pin = 0; + + if (swidget->prepared) { + struct sof_ipc4_audio_format *out_fmt; + + if (!swidget->num_output_pins || !available_fmt->num_output_formats || + !propagate_output) + return 0; + + if (unbound_output) { + ret = sof_ipc4_process_check_unbound_output_pins(sdev, swidget); + if (ret) + return ret; } + /* The pin formats are fixed, use the pin feeding the path. */ + out_fmt = sof_ipc4_process_get_output_pin_fmt(sdev, swidget, out_pin); + if (!out_fmt) + return -EINVAL; + + /* modify the pipeline params with the output format */ + return sof_ipc4_update_hw_params(sdev, pipeline_params, out_fmt, + BIT(SNDRV_PCM_HW_PARAM_FORMAT) | + BIT(SNDRV_PCM_HW_PARAM_CHANNELS) | + BIT(SNDRV_PCM_HW_PARAM_RATE)); + } + + if (available_fmt->num_input_formats) { input_fmt_index = sof_ipc4_init_input_audio_fmt(sdev, swidget, &process->base_config, pipeline_params, @@ -3172,8 +3276,10 @@ static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget, } /* Configure output audio format only if the module supports output */ - if (available_fmt->num_output_formats) { + if (swidget->num_output_pins && available_fmt->num_output_formats) { + u32 changed_params = available_fmt->changed_params; struct sof_ipc4_audio_format *in_fmt = NULL; + struct sof_ipc4_audio_format *out_fmt; struct sof_ipc4_pin_format *pin_fmt; if (available_fmt->num_input_formats) @@ -3182,21 +3288,42 @@ static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget, output_fmt_index = sof_ipc4_process_select_output_fmt(sdev, swidget, &process->base_config, available_fmt, in_fmt, - fe_params, dir); + fe_params, dir, out_pin); if (output_fmt_index < 0) return output_fmt_index; pin_fmt = &available_fmt->output_pin_fmts[output_fmt_index]; + out_fmt = &pin_fmt->audio_fmt; /* copy Pin output format for Pin 0 only */ - if (pin_fmt->pin_index == 0) { + if (pin_fmt->pin_index == 0) memcpy(&process->output_format, &pin_fmt->audio_fmt, sizeof(struct sof_ipc4_audio_format)); - /* modify the pipeline params with the output format */ - ret = sof_ipc4_update_hw_params(sdev, pipeline_params, - &process->output_format, - available_fmt->changed_params); + if (unbound_output) { + ret = sof_ipc4_process_check_unbound_output_pins(sdev, swidget); + if (ret) + return ret; + } + + /* + * The formats of the output pins of a multi-output module are fixed, + * use the format of the output pin feeding the path. + */ + if (swidget->num_output_pins > 1) { + out_fmt = sof_ipc4_process_get_output_pin_fmt(sdev, swidget, out_pin); + if (!out_fmt) + return -EINVAL; + + changed_params = BIT(SNDRV_PCM_HW_PARAM_FORMAT) | + BIT(SNDRV_PCM_HW_PARAM_CHANNELS) | + BIT(SNDRV_PCM_HW_PARAM_RATE); + } + + /* modify the pipeline params with the output format of the selected pin */ + if (propagate_output) { + ret = sof_ipc4_update_hw_params(sdev, pipeline_params, out_fmt, + changed_params); if (ret) return ret; } From 68a776294e3cf9816111e65e894f95b78581a0f9 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 23 Oct 2026 15:18:07 +0300 Subject: [PATCH 09/12] ASoC: SOF: ipc4-topology: Use the output pin of the path for copiers A copier has a single input pin and up to 4 output pins. The copier is configured with the output pin 0 format at init, the format of the other output pins is set with SET_SINK_FORMAT when they are bound. The copier prepare ignores the output pins: - the output format is looked up among the formats of all output pins, so a format of another pin can be picked as the pin 0 format, - the params for the next widget are always taken from the pin 0 format, even if the path is fed from another output pin. With a DAI copier fanning out to two pipelines: | pin0 (48K/16bit/2ch) -> B1 -> PCM1 DAI -> A ---| | pin1 (48K/32bit/2ch) -> B2 -> PCM2 if the copier input is 32-bit, the pin 1 format is selected as out_format and B1 receives 32-bit data. When PCM2 is started, B2 is prepared with the pin 0 format, while the copier will output 32-bit on pin 1. Select the output format used to initialize the copier exclusively from the formats declared for output pin 0. Separately, propagate the format of the output pin feeding each downstream connection. For pin 0, use the initialization format. For higher-numbered pins, select from that pin's topology formats using the copier input format as reference. If the pin has no declared formats, propagate the pin 0 format, as before. When setting the sink format of an output pin, check that the input format of the sink is one of the formats listed for the pin in topology. With no downstream connection, skip output parameter propagation. Without output pin bindings the pin is allocated at bind time: propagate the pin 0 format and let the bind set the format of the allocated pin from the input format of the sink. Signed-off-by: Peter Ujfalusi --- sound/soc/sof/ipc4-topology.c | 185 +++++++++++++++++++++++++++------- 1 file changed, 146 insertions(+), 39 deletions(-) diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index c43ba3afa5160a..472d778fd1f2ad 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -2231,13 +2231,98 @@ static void sof_ipc4_host_config(struct snd_sof_dev *sdev, struct snd_sof_widget copier_data->gtw_cfg.node_id |= SOF_IPC4_NODE_INDEX(host_dma_id); } +/* + * Return the first output format of @pin of a copier if all formats of the pin + * have the same bit depth, NULL otherwise. + */ +static struct sof_ipc4_audio_format * +sof_ipc4_copier_get_pin_bitdepth_ref(struct sof_ipc4_available_audio_format *available_fmt, + int pin) +{ + struct sof_ipc4_pin_format *pin_fmts = available_fmt->output_pin_fmts; + struct sof_ipc4_audio_format *ref = NULL; + int i; + + for (i = 0; i < available_fmt->num_output_formats; i++) { + struct sof_ipc4_audio_format *fmt = &pin_fmts[i].audio_fmt; + + if (pin_fmts[i].pin_index != pin) + continue; + + if (!ref) { + ref = fmt; + continue; + } + + if (SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(fmt->fmt_cfg) != + SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(ref->fmt_cfg)) + return NULL; + } + + return ref; +} + +/* + * Modify the params for the next widget with the format of the copier output pin + * feeding the path. Output pin 0 uses the format the copier is configured with. + * The format of other pins is set at bind time, select it from the formats of the + * pin in topology with the copier input format as reference, like for pin 0. Pins + * without formats in topology fall back to the pin 0 format. + * + * Without output pin bindings the pin is allocated at bind time, the pin 0 format + * is propagated and the format of the bound pin is set from the sink's input format + * when the copier is bound. + */ +static int sof_ipc4_copier_set_output_params(struct snd_sof_dev *sdev, + struct snd_sof_widget *swidget, + struct sof_ipc4_copier *ipc4_copier, + struct snd_pcm_hw_params *pipeline_params, + int out_pin) +{ + struct sof_ipc4_available_audio_format *available_fmt = &ipc4_copier->available_fmt; + struct sof_ipc4_copier_data *copier_data = &ipc4_copier->data; + struct sof_ipc4_audio_format *out_fmt = &copier_data->out_format; + + if (out_pin == SOF_OUTPUT_PIN_NONE) + return 0; + + if (out_pin > 0 && + sof_ipc4_get_first_pin_fmt(available_fmt->output_pin_fmts, + available_fmt->num_output_formats, out_pin) >= 0) { + struct sof_ipc4_audio_format *in_fmt = &copier_data->base_config.audio_fmt; + struct sof_ipc4_audio_format *ref_fmt; + u32 channels, valid_bits; + int i; + + ref_fmt = sof_ipc4_copier_get_pin_bitdepth_ref(available_fmt, out_pin); + if (!ref_fmt) + ref_fmt = in_fmt; + + channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(in_fmt->fmt_cfg); + valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(ref_fmt->fmt_cfg); + i = sof_ipc4_init_output_audio_fmt(sdev, swidget, NULL, available_fmt, out_pin, + in_fmt->sampling_frequency, channels, + valid_bits, + sof_ipc4_fmt_to_type(ref_fmt)); + if (i < 0) + return i; + + out_fmt = &available_fmt->output_pin_fmts[i].audio_fmt; + } + + return sof_ipc4_update_hw_params(sdev, pipeline_params, out_fmt, + BIT(SNDRV_PCM_HW_PARAM_FORMAT) | + BIT(SNDRV_PCM_HW_PARAM_CHANNELS) | + BIT(SNDRV_PCM_HW_PARAM_RATE)); +} + static int sof_ipc4_copier_module_update_params(struct snd_sof_widget *swidget, - struct snd_pcm_hw_params *pipeline_params) + struct snd_pcm_hw_params *pipeline_params, + int out_pin) { struct snd_soc_component *scomp = swidget->scomp; struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); - struct sof_ipc4_copier_data *copier_data; struct sof_ipc4_copier *ipc4_copier; switch (swidget->id) { @@ -2245,7 +2330,6 @@ sof_ipc4_copier_module_update_params(struct snd_sof_widget *swidget, case snd_soc_dapm_aif_out: case snd_soc_dapm_buffer: ipc4_copier = swidget->private; - copier_data = &ipc4_copier->data; break; case snd_soc_dapm_dai_in: case snd_soc_dapm_dai_out: @@ -2260,7 +2344,6 @@ sof_ipc4_copier_module_update_params(struct snd_sof_widget *swidget, dai = swidget->private; ipc4_copier = (struct sof_ipc4_copier *)dai->private; - copier_data = &ipc4_copier->data; break; } @@ -2271,20 +2354,19 @@ sof_ipc4_copier_module_update_params(struct snd_sof_widget *swidget, } /* modify the input params for the next widget */ - return sof_ipc4_update_hw_params(sdev, pipeline_params, - &copier_data->out_format, - BIT(SNDRV_PCM_HW_PARAM_FORMAT) | - BIT(SNDRV_PCM_HW_PARAM_CHANNELS) | - BIT(SNDRV_PCM_HW_PARAM_RATE)); + return sof_ipc4_copier_set_output_params(sdev, swidget, ipc4_copier, + pipeline_params, out_pin); } static int _sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, struct snd_pcm_hw_params *fe_params, struct snd_sof_platform_stream_params *platform_params, - struct snd_pcm_hw_params *pipeline_params, int dir) + struct snd_pcm_hw_params *pipeline_params, int dir, + int out_pin) { struct sof_ipc4_available_audio_format *available_fmt; + struct sof_ipc4_audio_format *bitdepth_ref_fmt; struct snd_soc_component *scomp = swidget->scomp; struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct sof_ipc4_copier_data *copier_data; @@ -2456,10 +2538,12 @@ _sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, if (input_fmt_index < 0) return input_fmt_index; - /* set the reference params for output format selection */ - single_output_bitdepth = sof_ipc4_copier_is_single_bitdepth(sdev, - available_fmt->output_pin_fmts, - available_fmt->num_output_formats); + /* + * Set the reference params for output format selection. The copier is configured + * with the output pin 0 format, the formats of other pins are set at bind time. + */ + bitdepth_ref_fmt = sof_ipc4_copier_get_pin_bitdepth_ref(available_fmt, 0); + single_output_bitdepth = !!bitdepth_ref_fmt; switch (swidget->id) { case snd_soc_dapm_aif_in: case snd_soc_dapm_dai_out: @@ -2501,34 +2585,24 @@ _sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, } /* - * if the output format is the same across all available output formats, choose - * that as the reference. + * if the output bit depth is the same across all available pin 0 output formats, + * choose that as the reference. */ if (single_output_bitdepth) { - struct sof_ipc4_audio_format *out_fmt; - - out_fmt = &available_fmt->output_pin_fmts[0].audio_fmt; out_ref_valid_bits = - SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(out_fmt->fmt_cfg); - out_ref_type = sof_ipc4_fmt_to_type(out_fmt); + SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(bitdepth_ref_fmt->fmt_cfg); + out_ref_type = sof_ipc4_fmt_to_type(bitdepth_ref_fmt); } output_fmt_index = sof_ipc4_init_output_audio_fmt(sdev, swidget, &copier_data->base_config, - available_fmt, SOF_OUTPUT_PIN_ANY, - out_ref_rate, + available_fmt, 0, out_ref_rate, out_ref_channels, out_ref_valid_bits, out_ref_type); if (output_fmt_index < 0) return output_fmt_index; - /* - * Set the output format. Current topology defines pin 0 input and output formats in pairs. - * This assumes that the pin 0 formats are defined before all other pins. - * So pick the output audio format with the same index as the chosen - * input format. This logic will need to be updated when the format definitions - * in topology change. - */ + /* Set the output format, which is the format of output pin 0 */ memcpy(&copier_data->out_format, &available_fmt->output_pin_fmts[output_fmt_index].audio_fmt, sizeof(struct sof_ipc4_audio_format)); @@ -2651,11 +2725,8 @@ _sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, } /* modify the input params for the next widget */ - ret = sof_ipc4_update_hw_params(sdev, pipeline_params, - &copier_data->out_format, - BIT(SNDRV_PCM_HW_PARAM_FORMAT) | - BIT(SNDRV_PCM_HW_PARAM_CHANNELS) | - BIT(SNDRV_PCM_HW_PARAM_RATE)); + ret = sof_ipc4_copier_set_output_params(sdev, swidget, ipc4_copier, + pipeline_params, out_pin); if (ret) return ret; @@ -2767,11 +2838,12 @@ sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, { if (swidget->prepared) return sof_ipc4_copier_module_update_params(swidget, - pipeline_params); + pipeline_params, + out_pin); return _sof_ipc4_prepare_copier_module(swidget, fe_params, platform_params, pipeline_params, - dir); + dir, out_pin); } static int sof_ipc4_prepare_gain_module(struct snd_sof_widget *swidget, @@ -4098,19 +4170,25 @@ static int sof_ipc4_set_copier_sink_format(struct snd_sof_dev *sdev, { struct sof_ipc4_copier_config_set_sink_format format; const struct sof_ipc_ops *iops = sdev->ipc->ops; + struct sof_ipc4_available_audio_format *available_fmt; struct sof_ipc4_base_module_cfg *src_config; const struct sof_ipc4_audio_format *pin_fmt; struct sof_ipc4_fw_module *fw_module; + struct sof_ipc4_copier *src_copier; struct sof_ipc4_msg msg = {{ 0 }}; + bool pin_has_fmts = false; + int i; if (WIDGET_IS_DAI(src_widget->id)) { struct snd_sof_dai *dai = src_widget->private; - src_config = dai->private; + src_copier = dai->private; } else { - src_config = src_widget->private; + src_copier = src_widget->private; } + src_config = &src_copier->data.base_config; + available_fmt = &src_copier->available_fmt; fw_module = src_widget->module_info; format.sink_id = sroute->src_queue_id; @@ -4125,6 +4203,35 @@ static int sof_ipc4_set_copier_sink_format(struct snd_sof_dev *sdev, return -EINVAL; } + /* + * If topology lists formats for the copier output pin, the input format of the + * sink must be one of them. + */ + for (i = 0; i < available_fmt->num_output_formats; i++) { + struct sof_ipc4_pin_format *out_pin_fmt = &available_fmt->output_pin_fmts[i]; + struct sof_ipc4_audio_format *fmt = &out_pin_fmt->audio_fmt; + + if (out_pin_fmt->pin_index != sroute->src_queue_id) + continue; + + pin_has_fmts = true; + if (fmt->sampling_frequency == pin_fmt->sampling_frequency && + SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(fmt->fmt_cfg) == + SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(pin_fmt->fmt_cfg) && + SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(fmt->fmt_cfg) == + SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(pin_fmt->fmt_cfg) && + sof_ipc4_fmt_to_type(fmt) == sof_ipc4_fmt_to_type(pin_fmt)) + break; + } + + if (pin_has_fmts && i == available_fmt->num_output_formats) { + dev_err(sdev->dev, + "Input format of %s:%d is not supported by output pin %d of %s\n", + sink_widget->widget->name, sroute->dst_queue_id, + sroute->src_queue_id, src_widget->widget->name); + return -EINVAL; + } + memcpy(&format.sink_fmt, pin_fmt, sizeof(format.sink_fmt)); msg.data_size = sizeof(format); From f269dce1c22a42dbde493e9e404be68fdd2a0666 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Thu, 22 Oct 2026 17:52:11 +0300 Subject: [PATCH 10/12] ASoC: SOF: ipc4-topology: Handle a path entering a module on input pin > 0 The input format of a module is looked up among its pin 0 input formats with the params of the source widget in the path being prepared. This assumes that the path enters the module on input pin 0. A module with multiple input pins can be reached first via another pin, depending on the order of the widgets in the widget list. For example the ECNS module receives 16kHz/32bit/4ch from dmic16k on pin 0 and 48kHz/32bit/4ch from dmic01 on pin 1. If the path from dmic01 is walked first the 48kHz params are looked up among the pin 0 formats and the prepare fails, even though they are valid for pin 1. It only works today because the dmic16k path happens to be walked first. The base config of the module is always set from the pin 0 input format. If the params do not match any pin 0 format but match the format of another input pin, the path enters the module on that pin: use the pin 0 format for the base config. This is only possible if pin 0 has a single format, otherwise the format to configure is unknown: modules with multiple pin 0 formats must be prepared first via the path entering on pin 0. Fail the prepare with an explicit error if this is not the case. The fallback is only taken where the lookup used to fail, the selection for paths entering on pin 0 is not changed. Signed-off-by: Peter Ujfalusi --- sound/soc/sof/ipc4-topology.c | 90 +++++++++++++++++++++++++++-------- 1 file changed, 69 insertions(+), 21 deletions(-) diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index 472d778fd1f2ad..3b7a1fc147f490 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -275,6 +275,15 @@ static bool sof_ipc4_is_same_fmt(const struct sof_ipc4_audio_format *a, sof_ipc4_fmt_to_type(a) == sof_ipc4_fmt_to_type(b); } +static bool sof_ipc4_fmt_matches_params(const struct sof_ipc4_audio_format *fmt, + u32 rate, u32 channels, u32 valid_bits, u32 type) +{ + return fmt->sampling_frequency == rate && + SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(fmt->fmt_cfg) == channels && + SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(fmt->fmt_cfg) == valid_bits && + sof_ipc4_fmt_to_type(fmt) == type; +} + /* Check if @pin_fmt belongs to @pin, SOF_OUTPUT_PIN_ANY matches all pins */ static bool sof_ipc4_is_pin_fmt(struct sof_ipc4_pin_format *pin_fmt, int pin) { @@ -1683,18 +1692,11 @@ static int sof_ipc4_init_output_audio_fmt(struct snd_sof_dev *sdev, for (i = 0; i < pin_fmts_size; i++) { struct sof_ipc4_audio_format *fmt = &pin_fmts[i].audio_fmt; - u32 _out_rate, _out_channels, _out_valid_bits, _out_type; - if (!sof_ipc4_is_pin_fmt(&pin_fmts[i], pin)) continue; - _out_rate = fmt->sampling_frequency; - _out_channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(fmt->fmt_cfg); - _out_valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(fmt->fmt_cfg); - _out_type = sof_ipc4_fmt_to_type(fmt); - - if (_out_rate == out_ref_rate && _out_channels == out_ref_channels && - _out_valid_bits == out_ref_valid_bits && _out_type == out_ref_type) + if (sof_ipc4_fmt_matches_params(fmt, out_ref_rate, out_ref_channels, + out_ref_valid_bits, out_ref_type)) goto out_fmt; } @@ -1755,6 +1757,53 @@ static int sof_ipc4_get_sample_type(struct snd_sof_dev *sdev, struct snd_pcm_hw_ } } +/* + * The params do not match any pin 0 input format: check if they match the format + * of another input pin, meaning that the path being prepared enters the module on + * that pin. The module is configured with the pin 0 input format, which is only + * known if pin 0 has a single format. With multiple pin 0 formats the path + * entering on pin 0 must be prepared first. + * + * Returns the index of the pin 0 format, -ENOENT if the params do not match any + * other pin either, -EINVAL if pin 0 does not have a single format. + */ +static int sof_ipc4_get_input_fmt_via_other_pin(struct snd_sof_dev *sdev, + struct snd_sof_widget *swidget, + struct sof_ipc4_pin_format *pin_fmts, + u32 pin_fmts_size, u32 rate, u32 channels, + u32 valid_bits, u32 type) +{ + int match_pin = -1; + int i; + + for (i = 0; i < pin_fmts_size; i++) { + struct sof_ipc4_audio_format *fmt = &pin_fmts[i].audio_fmt; + + if (!pin_fmts[i].pin_index) + continue; + + if (sof_ipc4_fmt_matches_params(fmt, rate, channels, valid_bits, type)) { + match_pin = pin_fmts[i].pin_index; + break; + } + } + + if (match_pin < 0) + return -ENOENT; + + if (!sof_ipc4_is_single_format(sdev, pin_fmts, pin_fmts_size, 0)) { + dev_err(sdev->dev, + "%s: params match input pin %d, pin 0 format is unknown\n", + swidget->widget->name, match_pin); + return -EINVAL; + } + + dev_dbg(sdev->dev, "%s: params match input pin %d, using the pin 0 format\n", + swidget->widget->name, match_pin); + + return sof_ipc4_get_first_pin_fmt(pin_fmts, pin_fmts_size, 0); +} + static int sof_ipc4_init_input_audio_fmt(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget, struct sof_ipc4_base_module_cfg *base_config, @@ -1763,10 +1812,6 @@ static int sof_ipc4_init_input_audio_fmt(struct snd_sof_dev *sdev, { struct sof_ipc4_pin_format *pin_fmts = available_fmt->input_pin_fmts; u32 pin_fmts_size = available_fmt->num_input_formats; - u32 valid_bits; - u32 channels; - u32 rate; - u32 type; bool single_format; int sample_valid_bits; int sample_type; @@ -1800,22 +1845,25 @@ static int sof_ipc4_init_input_audio_fmt(struct snd_sof_dev *sdev, if (pin_fmts[i].pin_index) continue; - rate = fmt->sampling_frequency; - channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(fmt->fmt_cfg); - valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(fmt->fmt_cfg); - type = sof_ipc4_fmt_to_type(fmt); - if (params_rate(params) == rate && params_channels(params) == channels && - sample_valid_bits == valid_bits && sample_type == type) + if (sof_ipc4_fmt_matches_params(fmt, params_rate(params), params_channels(params), + sample_valid_bits, sample_type)) break; } - if (i == pin_fmts_size) { + if (i == pin_fmts_size) + i = sof_ipc4_get_input_fmt_via_other_pin(sdev, swidget, pin_fmts, pin_fmts_size, + params_rate(params), + params_channels(params), + sample_valid_bits, sample_type); + + if (i == -ENOENT) dev_err(sdev->dev, "%s: Unsupported audio format: %uHz, %ubit, %u channels, type: %d\n", __func__, params_rate(params), sample_valid_bits, params_channels(params), sample_type); + + if (i < 0) return -EINVAL; - } in_fmt: /* copy input format */ From 98ef8f342fbc3a97d4237fa9d293a9db193efb34 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 23 Oct 2026 16:35:57 +0300 Subject: [PATCH 11/12] ASoC: SOF: ipc4-topology: Propagate the full output format of multi-input process modules The pipeline params are updated with the output format of a process module based on the changed_params mask, which is evaluated between the input and output formats of the module. When the path enters a multi-input module on an input pin other than pin 0 the pipeline params carry the format of that pin while the output format is derived from the pin 0 format of the module. The params not flagged as changed would be propagated from the wrong pin. This only worked because the changed_params is evaluated over the formats of all pins and the format of the other input pin usually differs from the output format. Update all params with the output format for modules with multiple input pins. Signed-off-by: Peter Ujfalusi --- sound/soc/sof/ipc4-topology.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index 3b7a1fc147f490..f61edb191607bc 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -3438,6 +3438,15 @@ static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget, changed_params = BIT(SNDRV_PCM_HW_PARAM_FORMAT) | BIT(SNDRV_PCM_HW_PARAM_CHANNELS) | BIT(SNDRV_PCM_HW_PARAM_RATE); + } else if (swidget->num_input_pins > 1) { + /* + * The path might enter the module on an input pin other than + * pin 0 and the pipeline params might not match the pin 0 + * format the output is derived from. + */ + changed_params = BIT(SNDRV_PCM_HW_PARAM_FORMAT) | + BIT(SNDRV_PCM_HW_PARAM_CHANNELS) | + BIT(SNDRV_PCM_HW_PARAM_RATE); } /* modify the pipeline params with the output format of the selected pin */ From 9fe415d3b9baf0cba3168007052ae8361cae7520 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 23 Oct 2026 16:36:30 +0300 Subject: [PATCH 12/12] ASoC: SOF: ipc4-topology: Evaluate the changed params on pin 0 formats only The changed_params mask is evaluated by comparing all input formats with all output formats of a module regardless of the pin they belong to. For modules with multiple pins this results in params flagged as changed which are not changed by the module on its data path. For example the google-rtc-aec has the 2 channel echo reference on input pin 1 and with 4 channel microphones it has a 4 channel format on input pin 0 and on the output, but the channels are flagged as changed. Only compare the formats of input pin 0 and output pin 0. The params of a path leaving a multi-output module on other pins or entering a multi-input module are updated with the full output format, they are not affected by the mask. Signed-off-by: Peter Ujfalusi --- sound/soc/sof/ipc4-topology.c | 11 +++++++++++ sound/soc/sof/ipc4-topology.h | 4 ++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index f61edb191607bc..54437460d5404e 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -475,6 +475,11 @@ sof_ipc4_get_input_pin_audio_fmt(struct snd_sof_widget *swidget, int pin_index) return NULL; } +/* + * Evaluate the params changed by the module between its pin 0 input and pin 0 + * output formats. The formats of other pins are not on the data path from + * input pin 0 to output pin 0. + */ static void sof_ipc4_evaluate_params_change(struct sof_ipc4_available_audio_format *available_fmt) { @@ -485,12 +490,18 @@ sof_ipc4_evaluate_params_change(struct sof_ipc4_available_audio_format *availabl int i, j; for (i = 0; i < available_fmt->num_input_formats; i++) { + if (available_fmt->input_pin_fmts[i].pin_index) + continue; + fmt = &available_fmt->input_pin_fmts[i].audio_fmt; in_rate = fmt->sampling_frequency; in_channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(fmt->fmt_cfg); in_valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(fmt->fmt_cfg); for (j = 0; j < available_fmt->num_output_formats; j++) { + if (available_fmt->output_pin_fmts[j].pin_index) + continue; + fmt = &available_fmt->output_pin_fmts[j].audio_fmt; out_rate = fmt->sampling_frequency; out_channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(fmt->fmt_cfg); diff --git a/sound/soc/sof/ipc4-topology.h b/sound/soc/sof/ipc4-topology.h index b538b4183897a9..6238d9abaf560c 100644 --- a/sound/soc/sof/ipc4-topology.h +++ b/sound/soc/sof/ipc4-topology.h @@ -197,8 +197,8 @@ struct sof_ipc4_pin_format { * @input_pin_fmts: Available input pin formats * @num_input_formats: Number of input pin formats * @num_output_formats: Number of output pin formats - * @changed_params: Mask of changed params by the module instance between it's - * input and output formts (rate, channels, depth) + * @changed_params: Mask of changed params by the module instance between its + * pin 0 input and output formats (rate, channels, depth) */ struct sof_ipc4_available_audio_format { struct sof_ipc4_pin_format *output_pin_fmts;