From 644473b181f0f310e428301a2ed459f912eec7ea Mon Sep 17 00:00:00 2001
Hi,
The following series will enable multi-stream support for playback and capture streams. Currently only a single PCM can be connected to a DAI, with the multi-stream support it is possible to connect multiple PCMs to a single DAI.
To achieve this we need to make sure that DAIs/AIF are only set up once since other stream could be connected to it later.
We also need to introduce reference or use counting for widgets to make sure that they are not going to be destroyed while other streams are still using them.
With the multi-stream support we also need to extend our current locking scheme which worked well for simple paths.
The first patch was sent to the mailing list, but it is not yet applied and I have included in this series as it is a direct dependency: https://lore.kernel.org/alsa-devel/20230117121615.25690-1-peter.ujfalusi@lin...
Regards, Peter --- Peter Ujfalusi (3): ASoC: SOF: Avoid double decrementing use_count in sof_widget_setup on error ASoC: SOF: Protect swidget->use_count with mutex for kcontrol access race ASoC: SOF: ipc4-pcm: Do not run the trigger pipelines if no spipe is stored
Ranjani Sridharan (15): ASoC: SOF: ipc4-topology: No need to unbind routes within a pipeline ASoC: soc-pcm: Export widget_in_list() ASoC: SOF: sof-audio: Set up/free DAI/AIF widgets only once ASoC: SOF: sof-audio: Only process widgets in the connected widget list ASoC: SOF: pcm: do not free widgets during suspend trigger ASoC: SOF: topology: Set IPC-specific trigger order for DAI links ASoC: SOF: Introduce PCM setup/free PCM IPC ops ASoC: SOF: ipc4-pcm: Define pcm_setup/free ops ASoC: SOF: ipc4: Add flag to skip triggering pipelines during FE DAI trigger ASoC: SOF: sof-audio: Populate the PCM stream pipeline_info ASoC: SOF: ipc4-pcm: Use the PCM stream's pipeline_info during trigger ASoC: SOF: Introduce struct snd_sof_pipeline ASoC: SOF: ipc4-pcm: Rename 'data' variable to trigger_list ASoC: SOF: ipc4-pcm: Implement pipeline trigger reference counting ASoC: SOF: ipc4-topology: Protect pipeline free with mutex
include/sound/soc-dpcm.h | 2 + include/sound/sof/ipc4/header.h | 3 + sound/soc/soc-pcm.c | 3 +- sound/soc/sof/core.c | 1 + sound/soc/sof/intel/hda-dai.c | 92 ++------- sound/soc/sof/ipc3-control.c | 46 +++-- sound/soc/sof/ipc3-topology.c | 32 ++- sound/soc/sof/ipc4-control.c | 33 ++- sound/soc/sof/ipc4-pcm.c | 343 +++++++++++++++++++++++++------- sound/soc/sof/ipc4-priv.h | 2 + sound/soc/sof/ipc4-topology.c | 48 ++++- sound/soc/sof/ipc4-topology.h | 12 ++ sound/soc/sof/ipc4.c | 2 + sound/soc/sof/pcm.c | 5 +- sound/soc/sof/sof-audio.c | 226 ++++++++++++++------- sound/soc/sof/sof-audio.h | 54 ++++- sound/soc/sof/sof-priv.h | 1 + sound/soc/sof/topology.c | 114 +++++++---- 18 files changed, 714 insertions(+), 305 deletions(-)
From: Ranjani Sridharan ranjani.sridharan@linux.intel.com
The FW currently ignores unbinding routes if the source and sink widgets belong to the same pipeline. So no need to send the IPC at all in the first place.
Signed-off-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Reviewed-by: Bard Liao yung-chuan.liao@linux.intel.com Reviewed-by: Rander Wang rander.wang@intel.com Signed-off-by: Peter Ujfalusi peter.ujfalusi@linux.intel.com --- sound/soc/sof/ipc4-topology.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index ba99114e86a9..ae8ec98bb4eb 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -1805,12 +1805,19 @@ static int sof_ipc4_route_free(struct snd_sof_dev *sdev, struct snd_sof_route *s struct sof_ipc4_fw_module *sink_fw_module = sink_widget->module_info; struct sof_ipc4_msg msg = {{ 0 }}; u32 header, extension; - int ret; + int ret = 0;
dev_dbg(sdev->dev, "unbind modules %s:%d -> %s:%d\n", src_widget->widget->name, sroute->src_queue_id, sink_widget->widget->name, sroute->dst_queue_id);
+ /* + * routes belonging to the same pipeline will be disconnected by the FW when the pipeline + * is freed. So avoid sending this IPC which will be ignored by the FW anyway. + */ + if (src_widget->pipe_widget == sink_widget->pipe_widget) + goto out; + header = src_fw_module->man4_module_entry.id; header |= SOF_IPC4_MOD_INSTANCE(src_widget->instance_id); header |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_MOD_UNBIND); @@ -1829,7 +1836,7 @@ static int sof_ipc4_route_free(struct snd_sof_dev *sdev, struct snd_sof_route *s if (ret < 0) dev_err(sdev->dev, "failed to unbind modules %s -> %s\n", src_widget->widget->name, sink_widget->widget->name); - +out: sof_ipc4_put_queue_id(sink_widget, sroute->dst_queue_id, SOF_PIN_TYPE_SINK); sof_ipc4_put_queue_id(src_widget, sroute->src_queue_id, SOF_PIN_TYPE_SOURCE);
From: Ranjani Sridharan ranjani.sridharan@linux.intel.com
Export the widget_in_list() function to be used by other modules.
Signed-off-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com Reviewed-by: Bard Liao yung-chuan.liao@linux.intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Reviewed-by: Péter Ujfalusi peter.ujfalusi@linux.intel.com Signed-off-by: Peter Ujfalusi peter.ujfalusi@linux.intel.com --- include/sound/soc-dpcm.h | 2 ++ sound/soc/soc-pcm.c | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/include/sound/soc-dpcm.h b/include/sound/soc-dpcm.h index 2864aed72998..1e7d09556fe3 100644 --- a/include/sound/soc-dpcm.h +++ b/include/sound/soc-dpcm.h @@ -162,6 +162,8 @@ int dpcm_be_dai_prepare(struct snd_soc_pcm_runtime *fe, int stream); int dpcm_dapm_stream_event(struct snd_soc_pcm_runtime *fe, int dir, int event); bool dpcm_end_walk_at_be(struct snd_soc_dapm_widget *widget, enum snd_soc_dapm_direction dir); +int widget_in_list(struct snd_soc_dapm_widget_list *list, + struct snd_soc_dapm_widget *widget);
#define dpcm_be_dai_startup_rollback(fe, stream, last) \ dpcm_be_dai_stop(fe, stream, 0, last) diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index 579a44d81d9a..f6caa55ef322 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -1337,7 +1337,7 @@ static struct snd_soc_pcm_runtime *dpcm_get_be(struct snd_soc_card *card, return NULL; }
-static int widget_in_list(struct snd_soc_dapm_widget_list *list, +int widget_in_list(struct snd_soc_dapm_widget_list *list, struct snd_soc_dapm_widget *widget) { struct snd_soc_dapm_widget *w; @@ -1349,6 +1349,7 @@ static int widget_in_list(struct snd_soc_dapm_widget_list *list,
return 0; } +EXPORT_SYMBOL_GPL(widget_in_list);
bool dpcm_end_walk_at_be(struct snd_soc_dapm_widget *widget, enum snd_soc_dapm_direction dir) {
From: Ranjani Sridharan ranjani.sridharan@linux.intel.com
Calling the sof_widget_setup/free() for the DAI/AIF widgets inside the snd_soc_dapm_widget_for_each_sink_path() loop will end up setting up or freeing the widget multiple times if there are multiple paths leaving the widget. Fix this by moving the widget setup/free for the starting widget in each path outside the loop.
Signed-off-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com Reviewed-by: Bard Liao yung-chuan.liao@linux.intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Reviewed-by: Péter Ujfalusi peter.ujfalusi@linux.intel.com Signed-off-by: Peter Ujfalusi peter.ujfalusi@linux.intel.com --- sound/soc/sof/sof-audio.c | 48 +++++++++++++++------------------------ 1 file changed, 18 insertions(+), 30 deletions(-)
diff --git a/sound/soc/sof/sof-audio.c b/sound/soc/sof/sof-audio.c index 2dff3ae25d27..572ac6a0c9ac 100644 --- a/sound/soc/sof/sof-audio.c +++ b/sound/soc/sof/sof-audio.c @@ -358,19 +358,16 @@ static int sof_free_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dap int err; int ret = 0;
- /* free all widgets even in case of error to keep use counts balanced */ + if (widget->dobj.private) { + err = sof_widget_free(sdev, widget->dobj.private); + if (err < 0) + ret = err; + } + + /* free all widgets in the sink paths even in case of error to keep use counts balanced */ snd_soc_dapm_widget_for_each_sink_path(widget, p) { - if (!p->walking && p->sink->dobj.private && widget->dobj.private) { + if (!p->walking) { p->walking = true; - if (WIDGET_IS_AIF_OR_DAI(widget->id)) { - err = sof_widget_free(sdev, widget->dobj.private); - if (err < 0) - ret = err; - } - - err = sof_widget_free(sdev, p->sink->dobj.private); - if (err < 0) - ret = err;
err = sof_free_widgets_in_path(sdev, p->sink, dir); if (err < 0) @@ -393,32 +390,23 @@ static int sof_set_up_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_d struct snd_soc_dapm_path *p; int ret;
+ if (widget->dobj.private) { + ret = sof_widget_setup(sdev, widget->dobj.private); + if (ret < 0) + return ret; + } + snd_soc_dapm_widget_for_each_sink_path(widget, p) { - if (!p->walking && p->sink->dobj.private && widget->dobj.private) { + if (!p->walking) { p->walking = true; - if (WIDGET_IS_AIF_OR_DAI(widget->id)) { - ret = sof_widget_setup(sdev, widget->dobj.private); - if (ret < 0) - goto out; - } - - ret = sof_widget_setup(sdev, p->sink->dobj.private); - if (ret < 0) { - if (WIDGET_IS_AIF_OR_DAI(widget->id)) - sof_widget_free(sdev, widget->dobj.private); - goto out; - }
ret = sof_set_up_widgets_in_path(sdev, p->sink, dir); + p->walking = false; if (ret < 0) { - if (WIDGET_IS_AIF_OR_DAI(widget->id)) + if (widget->dobj.private) sof_widget_free(sdev, widget->dobj.private); - sof_widget_free(sdev, p->sink->dobj.private); - } -out: - p->walking = false; - if (ret < 0) return ret; + } } }
From: Ranjani Sridharan ranjani.sridharan@linux.intel.com
When walking the list of the widgets from the source to the sink, we accidentally also end up preparing/setting up the widgets that are not in the list of connected DAPM widgets associated with the PCM. Avoid this by checking if a widget is part of the connected DAPM widget list during widget prepare, unprepare, setup or free.
Signed-off-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com Reviewed-by: Bard Liao yung-chuan.liao@linux.intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Reviewed-by: Péter Ujfalusi peter.ujfalusi@linux.intel.com Signed-off-by: Peter Ujfalusi peter.ujfalusi@linux.intel.com --- sound/soc/sof/sof-audio.c | 51 +++++++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 15 deletions(-)
diff --git a/sound/soc/sof/sof-audio.c b/sound/soc/sof/sof-audio.c index 572ac6a0c9ac..b127b304298c 100644 --- a/sound/soc/sof/sof-audio.c +++ b/sound/soc/sof/sof-audio.c @@ -241,24 +241,32 @@ static int sof_setup_pipeline_connections(struct snd_sof_dev *sdev, if (!widget->dobj.private) continue;
- snd_soc_dapm_widget_for_each_sink_path(widget, p) + snd_soc_dapm_widget_for_each_sink_path(widget, p) { + if (!widget_in_list(list, p->sink)) + continue; + if (p->sink->dobj.private) { ret = sof_route_setup(sdev, widget, p->sink); if (ret < 0) return ret; } + } } } else { for_each_dapm_widgets(list, i, widget) { if (!widget->dobj.private) continue;
- snd_soc_dapm_widget_for_each_source_path(widget, p) + snd_soc_dapm_widget_for_each_source_path(widget, p) { + if (!widget_in_list(list, p->source)) + continue; + if (p->source->dobj.private) { ret = sof_route_setup(sdev, p->source, widget); if (ret < 0) return ret; } + } } }
@@ -266,7 +274,8 @@ static int sof_setup_pipeline_connections(struct snd_sof_dev *sdev, }
static void -sof_unprepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *widget) +sof_unprepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *widget, + struct snd_soc_dapm_widget_list *list) { const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg); struct snd_sof_widget *swidget = widget->dobj.private; @@ -287,9 +296,11 @@ sof_unprepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widg sink_unprepare: /* unprepare all widgets in the sink paths */ snd_soc_dapm_widget_for_each_sink_path(widget, p) { + if (!widget_in_list(list, p->sink)) + continue; if (!p->walking && p->sink->dobj.private) { p->walking = true; - sof_unprepare_widgets_in_path(sdev, p->sink); + sof_unprepare_widgets_in_path(sdev, p->sink, list); p->walking = false; } } @@ -299,7 +310,8 @@ 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, 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, + struct snd_soc_dapm_widget_list *list) { const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg); struct snd_sof_widget *swidget = widget->dobj.private; @@ -327,10 +339,13 @@ sof_prepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget 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)) + 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); + platform_params, pipeline_params, dir, + list); p->walking = false; if (ret < 0) { /* unprepare the source widget */ @@ -352,7 +367,7 @@ sof_prepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget * (DAI type for capture, AIF type for playback) */ static int sof_free_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *widget, - int dir) + int dir, struct snd_soc_dapm_widget_list *list) { struct snd_soc_dapm_path *p; int err; @@ -367,9 +382,12 @@ static int sof_free_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dap /* free all widgets in the sink paths even in case of error to keep use counts balanced */ snd_soc_dapm_widget_for_each_sink_path(widget, p) { if (!p->walking) { + if (!widget_in_list(list, p->sink)) + continue; + p->walking = true;
- err = sof_free_widgets_in_path(sdev, p->sink, dir); + err = sof_free_widgets_in_path(sdev, p->sink, dir, list); if (err < 0) ret = err; p->walking = false; @@ -385,7 +403,7 @@ static int sof_free_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dap * The error path in this function ensures that all successfully set up widgets getting freed. */ static int sof_set_up_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *widget, - int dir) + int dir, struct snd_soc_dapm_widget_list *list) { struct snd_soc_dapm_path *p; int ret; @@ -398,9 +416,12 @@ static int sof_set_up_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_d
snd_soc_dapm_widget_for_each_sink_path(widget, p) { if (!p->walking) { + if (!widget_in_list(list, p->sink)) + continue; + p->walking = true;
- ret = sof_set_up_widgets_in_path(sdev, p->sink, dir); + ret = sof_set_up_widgets_in_path(sdev, p->sink, dir, list); p->walking = false; if (ret < 0) { if (widget->dobj.private) @@ -435,11 +456,11 @@ sof_walk_widgets_in_order(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget_l
switch (op) { case SOF_WIDGET_SETUP: - ret = sof_set_up_widgets_in_path(sdev, widget, dir); + ret = sof_set_up_widgets_in_path(sdev, widget, dir, list); str = "set up"; break; case SOF_WIDGET_FREE: - ret = sof_free_widgets_in_path(sdev, widget, dir); + ret = sof_free_widgets_in_path(sdev, widget, dir, list); str = "free"; break; case SOF_WIDGET_PREPARE: @@ -455,12 +476,12 @@ sof_walk_widgets_in_order(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget_l */ memcpy(&pipeline_params, fe_params, sizeof(*fe_params));
- ret = sof_prepare_widgets_in_path(sdev, widget, fe_params, - platform_params, &pipeline_params, dir); + ret = sof_prepare_widgets_in_path(sdev, widget, fe_params, platform_params, + &pipeline_params, dir, list); break; } case SOF_WIDGET_UNPREPARE: - sof_unprepare_widgets_in_path(sdev, widget); + sof_unprepare_widgets_in_path(sdev, widget, list); break; default: dev_err(sdev->dev, "Invalid widget op %d\n", op);
From: Ranjani Sridharan ranjani.sridharan@linux.intel.com
IPC3 and IPC4 have different requirements for the order in which the FE CPU and BE CPU DAI trigger callbacks must be invoked. With a regular PCM start/stop, pipeline widgets are set up during hw_params and freed during hw_free.
But when the system is suspended when a PCM is running, pipeline widgets are freed during the SUSPEND trigger callback for the FE CPU DAI. In order to avoid freeing the pipeline widgets before the BE CPU DAI trigger is executed, the trigger order was modified in previous contributions in the PCM dai_link_fixup callback to make sure that the BE CPU DAI trigger stop/suspend is always invoked before the FE CPU DAI trigger. But this contradicts the firmware requirement for IPC4 w.r.t. ordering of pipeline triggers.
So, remove the freeing of pipeline widgets during FE CPU DAI suspend trigger and handle it during system suspend when the tear_down_all_pipelines() IPC op is invoked. This will be followed up with a patch to fix the trigger order for IPC4.
Signed-off-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Reviewed-by: Bard Liao yung-chuan.liao@linux.intel.com Reviewed-by: Péter Ujfalusi peter.ujfalusi@linux.intel.com Signed-off-by: Peter Ujfalusi peter.ujfalusi@linux.intel.com --- sound/soc/sof/ipc3-topology.c | 2 +- sound/soc/sof/ipc4-pcm.c | 12 ------------ sound/soc/sof/ipc4-topology.c | 2 +- sound/soc/sof/pcm.c | 5 +---- 4 files changed, 3 insertions(+), 18 deletions(-)
diff --git a/sound/soc/sof/ipc3-topology.c b/sound/soc/sof/ipc3-topology.c index 989395999d6e..72ac1725af0d 100644 --- a/sound/soc/sof/ipc3-topology.c +++ b/sound/soc/sof/ipc3-topology.c @@ -2264,7 +2264,7 @@ static int sof_tear_down_left_over_pipelines(struct snd_sof_dev *sdev) for_each_pcm_streams(dir) { struct snd_pcm_substream *substream = spcm->stream[dir].substream;
- if (!substream || !substream->runtime) + if (!substream || !substream->runtime || spcm->stream[dir].suspend_ignored) continue;
if (spcm->stream[dir].list) { diff --git a/sound/soc/sof/ipc4-pcm.c b/sound/soc/sof/ipc4-pcm.c index 96941bebc1f1..23de58d7d06b 100644 --- a/sound/soc/sof/ipc4-pcm.c +++ b/sound/soc/sof/ipc4-pcm.c @@ -183,7 +183,6 @@ static int sof_ipc4_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); struct sof_ipc4_copier *ipc4_copier; - struct snd_soc_dpcm *dpcm;
if (!dai) { dev_err(component->dev, "%s: No DAI found with name %s\n", __func__, @@ -205,17 +204,6 @@ static int sof_ipc4_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, rate->min = ipc4_copier->available_fmt.base_config->audio_fmt.sampling_frequency; rate->max = rate->min;
- /* - * Set trigger order for capture to SND_SOC_DPCM_TRIGGER_PRE. This is required - * to ensure that the BE DAI pipeline gets stopped/suspended before the FE DAI - * pipeline gets triggered and the pipeline widgets are freed. - */ - for_each_dpcm_fe(rtd, SNDRV_PCM_STREAM_CAPTURE, dpcm) { - struct snd_soc_pcm_runtime *fe = dpcm->fe; - - fe->dai_link->trigger[SNDRV_PCM_STREAM_CAPTURE] = SND_SOC_DPCM_TRIGGER_PRE; - } - switch (ipc4_copier->dai_type) { case SOF_DAI_INTEL_SSP: ipc4_ssp_dai_config_pcm_params_match(sdev, (char *)rtd->dai_link->name, params); diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index ae8ec98bb4eb..3938ff2d998b 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -2025,7 +2025,7 @@ static int sof_ipc4_tear_down_all_pipelines(struct snd_sof_dev *sdev, bool verif for_each_pcm_streams(dir) { struct snd_pcm_substream *substream = spcm->stream[dir].substream;
- if (!substream || !substream->runtime) + if (!substream || !substream->runtime || spcm->stream[dir].suspend_ignored) continue;
if (spcm->stream[dir].list) { diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c index 952fc698a586..34d40c5c629a 100644 --- a/sound/soc/sof/pcm.c +++ b/sound/soc/sof/pcm.c @@ -282,7 +282,6 @@ static int sof_pcm_trigger(struct snd_soc_component *component, const struct sof_ipc_pcm_ops *pcm_ops = sof_ipc_get_ops(sdev, pcm); struct snd_sof_pcm *spcm; bool reset_hw_params = false; - bool free_widget_list = false; bool ipc_first = false; int ret = 0;
@@ -326,7 +325,6 @@ static int sof_pcm_trigger(struct snd_soc_component *component, spcm->stream[substream->stream].suspend_ignored = true; return 0; } - free_widget_list = true; fallthrough; case SNDRV_PCM_TRIGGER_STOP: ipc_first = true; @@ -353,8 +351,7 @@ static int sof_pcm_trigger(struct snd_soc_component *component,
/* free PCM if reset_hw_params is set and the STOP IPC is successful */ if (!ret && reset_hw_params) - ret = sof_pcm_stream_free(sdev, substream, spcm, substream->stream, - free_widget_list); + ret = sof_pcm_stream_free(sdev, substream, spcm, substream->stream, false);
return ret; }
From: Ranjani Sridharan ranjani.sridharan@linux.intel.com
Add a new topology IPC op to set up DAI links and set the link trigger order to match the expectation based on the IPC type. Note that the link_setup op implementations for IPC3 and IPC4 are not identical and have contrasting trigger orders for playback and capture.
Signed-off-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Reviewed-by: Bard Liao yung-chuan.liao@linux.intel.com Reviewed-by: Péter Ujfalusi peter.ujfalusi@linux.intel.com Signed-off-by: Peter Ujfalusi peter.ujfalusi@linux.intel.com --- sound/soc/sof/ipc3-topology.c | 19 +++++++++++++++++++ sound/soc/sof/ipc4-topology.c | 19 +++++++++++++++++++ sound/soc/sof/sof-audio.h | 2 ++ sound/soc/sof/topology.c | 25 +++++++------------------ 4 files changed, 47 insertions(+), 18 deletions(-)
diff --git a/sound/soc/sof/ipc3-topology.c b/sound/soc/sof/ipc3-topology.c index 72ac1725af0d..3f52dfb19e01 100644 --- a/sound/soc/sof/ipc3-topology.c +++ b/sound/soc/sof/ipc3-topology.c @@ -2426,6 +2426,24 @@ static int sof_ipc3_parse_manifest(struct snd_soc_component *scomp, int index, return 0; }
+static int sof_ipc3_link_setup(struct snd_sof_dev *sdev, struct snd_soc_dai_link *link) +{ + if (link->no_pcm) + return 0; + + /* + * set default trigger order for all links. Exceptions to + * the rule will be handled in sof_pcm_dai_link_fixup() + * For playback, the sequence is the following: start FE, + * start BE, stop BE, stop FE; for Capture the sequence is + * inverted start BE, start FE, stop FE, stop BE + */ + link->trigger[SNDRV_PCM_STREAM_PLAYBACK] = SND_SOC_DPCM_TRIGGER_PRE; + link->trigger[SNDRV_PCM_STREAM_CAPTURE] = SND_SOC_DPCM_TRIGGER_POST; + + return 0; +} + /* token list for each topology object */ static enum sof_tokens host_token_list[] = { SOF_CORE_TOKENS, @@ -2537,4 +2555,5 @@ const struct sof_ipc_tplg_ops ipc3_tplg_ops = { .set_up_all_pipelines = sof_ipc3_set_up_all_pipelines, .tear_down_all_pipelines = sof_ipc3_tear_down_all_pipelines, .parse_manifest = sof_ipc3_parse_manifest, + .link_setup = sof_ipc3_link_setup, }; diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index 3938ff2d998b..b07a405516b1 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -2038,6 +2038,24 @@ static int sof_ipc4_tear_down_all_pipelines(struct snd_sof_dev *sdev, bool verif return 0; }
+static int sof_ipc4_link_setup(struct snd_sof_dev *sdev, struct snd_soc_dai_link *link) +{ + if (link->no_pcm) + return 0; + + /* + * set default trigger order for all links. Exceptions to + * the rule will be handled in sof_pcm_dai_link_fixup() + * For playback, the sequence is the following: start BE, + * start FE, stop FE, stop BE; for Capture the sequence is + * inverted start FE, start BE, stop BE, stop FE + */ + link->trigger[SNDRV_PCM_STREAM_PLAYBACK] = SND_SOC_DPCM_TRIGGER_POST; + link->trigger[SNDRV_PCM_STREAM_CAPTURE] = SND_SOC_DPCM_TRIGGER_PRE; + + return 0; +} + static enum sof_tokens common_copier_token_list[] = { SOF_COMP_TOKENS, SOF_AUDIO_FMT_NUM_TOKENS, @@ -2144,4 +2162,5 @@ const struct sof_ipc_tplg_ops ipc4_tplg_ops = { .parse_manifest = sof_ipc4_parse_manifest, .dai_get_clk = sof_ipc4_dai_get_clk, .tear_down_all_pipelines = sof_ipc4_tear_down_all_pipelines, + .link_setup = sof_ipc4_link_setup, }; diff --git a/sound/soc/sof/sof-audio.h b/sound/soc/sof/sof-audio.h index 8e4abb1f5f73..28062a0c3a43 100644 --- a/sound/soc/sof/sof-audio.h +++ b/sound/soc/sof/sof-audio.h @@ -180,6 +180,7 @@ struct sof_ipc_tplg_widget_ops { * @set_up_all_pipelines: Function pointer for setting up all topology pipelines * @tear_down_all_pipelines: Function pointer for tearing down all topology pipelines * @parse_manifest: Function pointer for ipc4 specific parsing of topology manifest + * @link_setup: Function pointer for IPC-specific DAI link set up * * Note: function pointers (ops) are optional */ @@ -201,6 +202,7 @@ struct sof_ipc_tplg_ops { int (*tear_down_all_pipelines)(struct snd_sof_dev *sdev, bool verify); int (*parse_manifest)(struct snd_soc_component *scomp, int index, struct snd_soc_tplg_manifest *man); + int (*link_setup)(struct snd_sof_dev *sdev, struct snd_soc_dai_link *link); };
/** struct snd_sof_tuple - Tuple info diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c index 560771ba8fb9..f67c39c47930 100644 --- a/sound/soc/sof/topology.c +++ b/sound/soc/sof/topology.c @@ -1813,26 +1813,15 @@ static int sof_link_load(struct snd_soc_component *scomp, int index, struct snd_ } link->platforms->name = dev_name(scomp->dev);
- /* - * Set nonatomic property for FE dai links as their trigger action - * involves IPC's. - */ + if (tplg_ops && tplg_ops->link_setup) { + ret = tplg_ops->link_setup(sdev, link); + if (ret < 0) + return ret; + } + + /* Set nonatomic property for FE dai links as their trigger action involves IPC's */ if (!link->no_pcm) { link->nonatomic = true; - - /* - * set default trigger order for all links. Exceptions to - * the rule will be handled in sof_pcm_dai_link_fixup() - * For playback, the sequence is the following: start FE, - * start BE, stop BE, stop FE; for Capture the sequence is - * inverted start BE, start FE, stop FE, stop BE - */ - link->trigger[SNDRV_PCM_STREAM_PLAYBACK] = - SND_SOC_DPCM_TRIGGER_PRE; - link->trigger[SNDRV_PCM_STREAM_CAPTURE] = - SND_SOC_DPCM_TRIGGER_POST; - - /* nothing more to do for FE dai links */ return 0; }
From: Ranjani Sridharan ranjani.sridharan@linux.intel.com
These will be used to perform IPC-specific PCM setup/free.
Signed-off-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com Reviewed-by: Libin Yang libin.yang@intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Reviewed-by: Péter Ujfalusi peter.ujfalusi@linux.intel.com Signed-off-by: Peter Ujfalusi peter.ujfalusi@linux.intel.com --- sound/soc/sof/sof-audio.h | 7 +++++++ sound/soc/sof/topology.c | 14 ++++++++++++++ 2 files changed, 21 insertions(+)
diff --git a/sound/soc/sof/sof-audio.h b/sound/soc/sof/sof-audio.h index 28062a0c3a43..bcde2ebaf022 100644 --- a/sound/soc/sof/sof-audio.h +++ b/sound/soc/sof/sof-audio.h @@ -85,6 +85,7 @@ struct snd_sof_widget; struct snd_sof_route; struct snd_sof_control; struct snd_sof_dai; +struct snd_sof_pcm;
struct snd_sof_dai_config_data { int dai_index; @@ -97,6 +98,10 @@ struct snd_sof_dai_config_data { * @hw_free: Function pointer for hw_free * @trigger: Function pointer for trigger * @dai_link_fixup: Function pointer for DAI link fixup + * @pcm_setup: Function pointer for IPC-specific PCM set up that can be used for allocating + * additional memory in the SOF PCM stream structure + * @pcm_free: Function pointer for PCM free that can be used for freeing any + * additional memory in the SOF PCM stream structure */ struct sof_ipc_pcm_ops { int (*hw_params)(struct snd_soc_component *component, struct snd_pcm_substream *substream, @@ -106,6 +111,8 @@ struct sof_ipc_pcm_ops { int (*trigger)(struct snd_soc_component *component, struct snd_pcm_substream *substream, int cmd); int (*dai_link_fixup)(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_params *params); + int (*pcm_setup)(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm); + void (*pcm_free)(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm); };
/** diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c index f67c39c47930..51f6fed45ae7 100644 --- a/sound/soc/sof/topology.c +++ b/sound/soc/sof/topology.c @@ -1669,6 +1669,7 @@ static int sof_dai_load(struct snd_soc_component *scomp, int index, struct snd_soc_tplg_pcm *pcm, struct snd_soc_dai *dai) { struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + const struct sof_ipc_pcm_ops *ipc_pcm_ops = sof_ipc_get_ops(sdev, pcm); struct snd_soc_tplg_stream_caps *caps; struct snd_soc_tplg_private *private = &pcm->priv; struct snd_sof_pcm *spcm; @@ -1696,6 +1697,13 @@ static int sof_dai_load(struct snd_soc_component *scomp, int index, spcm->pcm = *pcm; dev_dbg(scomp->dev, "tplg: load pcm %s\n", pcm->dai_name);
+ /* perform pcm set op */ + if (ipc_pcm_ops && ipc_pcm_ops->pcm_setup) { + ret = ipc_pcm_ops->pcm_setup(sdev, spcm); + if (ret < 0) + return ret; + } + dai_drv->dobj.private = spcm; list_add(&spcm->list, &sdev->pcm_list);
@@ -1773,6 +1781,8 @@ static int sof_dai_load(struct snd_soc_component *scomp, int index, static int sof_dai_unload(struct snd_soc_component *scomp, struct snd_soc_dobj *dobj) { + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + const struct sof_ipc_pcm_ops *ipc_pcm_ops = sof_ipc_get_ops(sdev, pcm); struct snd_sof_pcm *spcm = dobj->private;
/* free PCM DMA pages */ @@ -1782,6 +1792,10 @@ static int sof_dai_unload(struct snd_soc_component *scomp, if (spcm->pcm.capture) snd_dma_free_pages(&spcm->stream[SNDRV_PCM_STREAM_CAPTURE].page_table);
+ /* perform pcm free op */ + if (ipc_pcm_ops && ipc_pcm_ops->pcm_free) + ipc_pcm_ops->pcm_free(sdev, spcm); + /* remove from list and free spcm */ list_del(&spcm->list); kfree(spcm);
From: Ranjani Sridharan ranjani.sridharan@linux.intel.com
Define the pcm_setup/pcm_free ops for IPC4. Define a new struct snd_sof_pcm_stream_trigger_info and add a new field trigger_info of this type to struct snd_sof_pcm_stream. This will be used to save the list of pipelines that need to be triggered.
Signed-off-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com Reviewed-by: Libin Yang libin.yang@intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Reviewed-by: Péter Ujfalusi peter.ujfalusi@linux.intel.com Signed-off-by: Peter Ujfalusi peter.ujfalusi@linux.intel.com --- sound/soc/sof/ipc4-pcm.c | 36 ++++++++++++++++++++++++++++++++++++ sound/soc/sof/sof-audio.h | 11 +++++++++++ 2 files changed, 47 insertions(+)
diff --git a/sound/soc/sof/ipc4-pcm.c b/sound/soc/sof/ipc4-pcm.c index 23de58d7d06b..05515e8e6f57 100644 --- a/sound/soc/sof/ipc4-pcm.c +++ b/sound/soc/sof/ipc4-pcm.c @@ -215,8 +215,44 @@ static int sof_ipc4_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, return 0; }
+static void sof_ipc4_pcm_free(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm) +{ + struct snd_sof_pcm_stream_pipeline_list *pipeline_list; + int stream; + + for_each_pcm_streams(stream) { + pipeline_list = &spcm->stream[stream].pipeline_list; + kfree(pipeline_list->pipe_widgets); + pipeline_list->pipe_widgets = NULL; + } +} + +static int sof_ipc4_pcm_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm) +{ + struct snd_sof_pcm_stream_pipeline_list *pipeline_list; + struct sof_ipc4_fw_data *ipc4_data = sdev->private; + int stream; + + for_each_pcm_streams(stream) { + pipeline_list = &spcm->stream[stream].pipeline_list; + + /* allocate memory for max number of pipeline IDs */ + pipeline_list->pipe_widgets = kcalloc(ipc4_data->max_num_pipelines, + sizeof(struct snd_sof_widget *), + GFP_KERNEL); + if (!pipeline_list->pipe_widgets) { + sof_ipc4_pcm_free(sdev, spcm); + return -ENOMEM; + } + } + + return 0; +} + const struct sof_ipc_pcm_ops ipc4_pcm_ops = { .trigger = sof_ipc4_pcm_trigger, .hw_free = sof_ipc4_pcm_hw_free, .dai_link_fixup = sof_ipc4_pcm_dai_link_fixup, + .pcm_setup = sof_ipc4_pcm_setup, + .pcm_free = sof_ipc4_pcm_free, }; diff --git a/sound/soc/sof/sof-audio.h b/sound/soc/sof/sof-audio.h index bcde2ebaf022..bb5c61dd9b1e 100644 --- a/sound/soc/sof/sof-audio.h +++ b/sound/soc/sof/sof-audio.h @@ -285,6 +285,16 @@ struct sof_token_info { int count; };
+/** + * struct snd_sof_pcm_stream_pipeline_list - List of pipelines associated with a PCM stream + * @count: number of pipeline widgets in the @pipe_widgets array + * @pipe_widgets: array of pipeline widgets + */ +struct snd_sof_pcm_stream_pipeline_list { + u32 count; + struct snd_sof_widget **pipe_widgets; +}; + /* PCM stream, mapped to FW component */ struct snd_sof_pcm_stream { u32 comp_id; @@ -300,6 +310,7 @@ struct snd_sof_pcm_stream { * active or not while suspending the stream */ bool suspend_ignored; + struct snd_sof_pcm_stream_pipeline_list pipeline_list; };
/* ALSA SOF PCM device */
From: Ranjani Sridharan ranjani.sridharan@linux.intel.com
Add a new flag, skip_during_fe_trigger, to struct sof_ipc4_pipeline to skip triggering pipelines in the FE DAI trigger. Set this flag for the HDA DAI BE pipelines so that their BE pipeline will not be triggered in the FE DAI trigger. Also, move the trigger handling for all commands include START/PAUSE_RELEASE for the HDA DAI's to the backend DAI trigger ops.
For the SSP/DMIC/SDW cases, remove the BE DAI trigger as they involve no DMA operations and can be triggered in the FE DAI trigger. This is in preparation to perform batch triggering of all pipelines for the non-HDA case.
Signed-off-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com Reviewed-by: Libin Yang libin.yang@intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Reviewed-by: Péter Ujfalusi peter.ujfalusi@linux.intel.com Signed-off-by: Peter Ujfalusi peter.ujfalusi@linux.intel.com --- sound/soc/sof/intel/hda-dai.c | 92 +++++++---------------------------- sound/soc/sof/ipc4-pcm.c | 17 +------ sound/soc/sof/ipc4-topology.c | 1 + sound/soc/sof/ipc4-topology.h | 2 + 4 files changed, 21 insertions(+), 91 deletions(-)
diff --git a/sound/soc/sof/intel/hda-dai.c b/sound/soc/sof/intel/hda-dai.c index 1c3d4887aa30..98eebb4b07e6 100644 --- a/sound/soc/sof/intel/hda-dai.c +++ b/sound/soc/sof/intel/hda-dai.c @@ -450,6 +450,8 @@ static int ipc4_hda_dai_trigger(struct snd_pcm_substream *substream, { struct hdac_ext_stream *hext_stream = snd_soc_dai_get_dma_data(dai, substream); struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(dai->component); + struct snd_sof_widget *pipe_widget; + struct sof_ipc4_pipeline *pipeline; struct snd_soc_pcm_runtime *rtd; struct snd_sof_widget *swidget; struct snd_soc_dapm_widget *w; @@ -466,18 +468,30 @@ static int ipc4_hda_dai_trigger(struct snd_pcm_substream *substream,
w = snd_soc_dai_get_widget(dai, substream->stream); swidget = w->dobj.private; + pipe_widget = swidget->pipe_widget; + pipeline = pipe_widget->private;
switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: snd_hdac_ext_stream_start(hext_stream); + if (pipeline->state != SOF_IPC4_PIPE_PAUSED) { + ret = sof_ipc4_set_pipeline_state(sdev, pipe_widget->instance_id, + SOF_IPC4_PIPE_PAUSED); + if (ret < 0) + return ret; + pipeline->state = SOF_IPC4_PIPE_PAUSED; + } + + ret = sof_ipc4_set_pipeline_state(sdev, pipe_widget->instance_id, + SOF_IPC4_PIPE_RUNNING); + if (ret < 0) + return ret; + pipeline->state = SOF_IPC4_PIPE_RUNNING; break; case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_STOP: { - struct snd_sof_widget *pipe_widget = swidget->pipe_widget; - struct sof_ipc4_pipeline *pipeline = pipe_widget->private; - ret = sof_ipc4_set_pipeline_state(sdev, pipe_widget->instance_id, SOF_IPC4_PIPE_PAUSED); if (ret < 0) @@ -503,9 +517,6 @@ static int ipc4_hda_dai_trigger(struct snd_pcm_substream *substream, } case SNDRV_PCM_TRIGGER_PAUSE_PUSH: { - struct snd_sof_widget *pipe_widget = swidget->pipe_widget; - struct sof_ipc4_pipeline *pipeline = pipe_widget->private; - ret = sof_ipc4_set_pipeline_state(sdev, pipe_widget->instance_id, SOF_IPC4_PIPE_PAUSED); if (ret < 0) @@ -703,64 +714,6 @@ static const struct snd_soc_dai_ops ipc3_ssp_dai_ops = { .shutdown = ssp_dai_shutdown, };
-static int ipc4_be_dai_common_trigger(struct snd_soc_dai *dai, int cmd, int stream) -{ - struct snd_sof_widget *pipe_widget; - struct sof_ipc4_pipeline *pipeline; - struct snd_sof_widget *swidget; - struct snd_soc_dapm_widget *w; - struct snd_sof_dev *sdev; - int ret; - - w = snd_soc_dai_get_widget(dai, stream); - swidget = w->dobj.private; - pipe_widget = swidget->pipe_widget; - pipeline = pipe_widget->private; - sdev = snd_soc_component_get_drvdata(swidget->scomp); - - switch (cmd) { - case SNDRV_PCM_TRIGGER_SUSPEND: - case SNDRV_PCM_TRIGGER_STOP: - ret = sof_ipc4_set_pipeline_state(sdev, pipe_widget->instance_id, - SOF_IPC4_PIPE_PAUSED); - if (ret < 0) - return ret; - pipeline->state = SOF_IPC4_PIPE_PAUSED; - - ret = sof_ipc4_set_pipeline_state(sdev, pipe_widget->instance_id, - SOF_IPC4_PIPE_RESET); - if (ret < 0) - return ret; - pipeline->state = SOF_IPC4_PIPE_RESET; - break; - case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - ret = sof_ipc4_set_pipeline_state(sdev, pipe_widget->instance_id, - SOF_IPC4_PIPE_PAUSED); - if (ret < 0) - return ret; - pipeline->state = SOF_IPC4_PIPE_PAUSED; - break; - default: - break; - } - - return 0; -} - -static int ipc4_be_dai_trigger(struct snd_pcm_substream *substream, - int cmd, struct snd_soc_dai *dai) -{ - return ipc4_be_dai_common_trigger(dai, cmd, substream->stream); -} - -static const struct snd_soc_dai_ops ipc4_dmic_dai_ops = { - .trigger = ipc4_be_dai_trigger, -}; - -static const struct snd_soc_dai_ops ipc4_ssp_dai_ops = { - .trigger = ipc4_be_dai_trigger, -}; - void hda_set_dai_drv_ops(struct snd_sof_dev *sdev, struct snd_sof_dsp_ops *ops) { int i; @@ -785,14 +738,6 @@ void hda_set_dai_drv_ops(struct snd_sof_dev *sdev, struct snd_sof_dsp_ops *ops) struct sof_ipc4_fw_data *ipc4_data = sdev->private;
for (i = 0; i < ops->num_drv; i++) { - if (strstr(ops->drv[i].name, "DMIC")) { - ops->drv[i].ops = &ipc4_dmic_dai_ops; - continue; - } - if (strstr(ops->drv[i].name, "SSP")) { - ops->drv[i].ops = &ipc4_ssp_dai_ops; - continue; - } #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC) if (strstr(ops->drv[i].name, "iDisp") || strstr(ops->drv[i].name, "Analog") || @@ -804,9 +749,6 @@ void hda_set_dai_drv_ops(struct snd_sof_dev *sdev, struct snd_sof_dsp_ops *ops) if (!hda_use_tplg_nhlt) ipc4_data->nhlt = intel_nhlt_init(sdev->dev);
- if (IS_ENABLED(CONFIG_SND_SOC_SOF_INTEL_SOUNDWIRE)) - sdw_callback.trigger = ipc4_be_dai_common_trigger; - break; } default: diff --git a/sound/soc/sof/ipc4-pcm.c b/sound/soc/sof/ipc4-pcm.c index 05515e8e6f57..db9d0adb2717 100644 --- a/sound/soc/sof/ipc4-pcm.c +++ b/sound/soc/sof/ipc4-pcm.c @@ -58,25 +58,10 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, if (!swidget) continue;
- /* - * set pipeline state for both FE and BE pipelines for RUNNING state. - * For PAUSE/RESET, set the pipeline state only for the FE pipeline. - */ - switch (state) { - case SOF_IPC4_PIPE_PAUSED: - case SOF_IPC4_PIPE_RESET: - if (!WIDGET_IS_AIF(swidget->id)) - continue; - break; - default: - break; - } - - /* find pipeline widget for the pipeline that this widget belongs to */ pipeline_widget = swidget->pipe_widget; pipeline = (struct sof_ipc4_pipeline *)pipeline_widget->private;
- if (pipeline->state == state) + if (pipeline->state == state || pipeline->skip_during_fe_trigger) continue;
/* first set the pipeline to PAUSED state */ diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index b07a405516b1..f3b1a7f81216 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -1869,6 +1869,7 @@ static int sof_ipc4_dai_config(struct snd_sof_dev *sdev, struct snd_sof_widget * case SOF_DAI_INTEL_HDA: gtw_attr = ipc4_copier->gtw_attr; gtw_attr->lp_buffer_alloc = pipeline->lp_mode; + pipeline->skip_during_fe_trigger = true; fallthrough; case SOF_DAI_INTEL_ALH: copier_data->gtw_cfg.node_id &= ~SOF_IPC4_NODE_INDEX_MASK; diff --git a/sound/soc/sof/ipc4-topology.h b/sound/soc/sof/ipc4-topology.h index 8dbbf69b0eb7..028b5d91b9db 100644 --- a/sound/soc/sof/ipc4-topology.h +++ b/sound/soc/sof/ipc4-topology.h @@ -73,6 +73,7 @@ * @mem_usage: Memory usage * @state: Pipeline state * @msg: message structure for pipeline + * @skip_during_fe_trigger: skip triggering this pipeline during the FE DAI trigger */ struct sof_ipc4_pipeline { uint32_t priority; @@ -80,6 +81,7 @@ struct sof_ipc4_pipeline { uint32_t mem_usage; int state; struct sof_ipc4_msg msg; + bool skip_during_fe_trigger; };
/**
From: Ranjani Sridharan ranjani.sridharan@linux.intel.com
Populate the pipeline_info for the PCM stream with the list of pipeline widgets that need to be handled during the PCM trigger. This will be used in the IPC-specific PCM trigger op to trigger the pipelines.
Signed-off-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com Reviewed-by: Libin Yang libin.yang@intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Reviewed-by: Péter Ujfalusi peter.ujfalusi@linux.intel.com Signed-off-by: Peter Ujfalusi peter.ujfalusi@linux.intel.com --- sound/soc/sof/sof-audio.c | 69 +++++++++++++++++++++++++++++---------- 1 file changed, 52 insertions(+), 17 deletions(-)
diff --git a/sound/soc/sof/sof-audio.c b/sound/soc/sof/sof-audio.c index b127b304298c..e6796c59e04b 100644 --- a/sound/soc/sof/sof-audio.c +++ b/sound/soc/sof/sof-audio.c @@ -367,8 +367,9 @@ sof_prepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget * (DAI type for capture, AIF type for playback) */ static int sof_free_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *widget, - int dir, struct snd_soc_dapm_widget_list *list) + int dir, struct snd_sof_pcm *spcm) { + struct snd_soc_dapm_widget_list *list = spcm->stream[dir].list; struct snd_soc_dapm_path *p; int err; int ret = 0; @@ -387,7 +388,7 @@ static int sof_free_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dap
p->walking = true;
- err = sof_free_widgets_in_path(sdev, p->sink, dir, list); + err = sof_free_widgets_in_path(sdev, p->sink, dir, spcm); if (err < 0) ret = err; p->walking = false; @@ -403,17 +404,44 @@ static int sof_free_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dap * The error path in this function ensures that all successfully set up widgets getting freed. */ static int sof_set_up_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *widget, - int dir, struct snd_soc_dapm_widget_list *list) + int dir, struct snd_sof_pcm *spcm) { + struct snd_sof_pcm_stream_pipeline_list *pipeline_list = &spcm->stream[dir].pipeline_list; + struct snd_soc_dapm_widget_list *list = spcm->stream[dir].list; + struct snd_sof_widget *swidget = widget->dobj.private; + struct snd_sof_widget *pipe_widget; struct snd_soc_dapm_path *p; int ret;
- if (widget->dobj.private) { + if (swidget) { + int i; + ret = sof_widget_setup(sdev, widget->dobj.private); if (ret < 0) return ret; + + /* skip populating the pipe_widgets array if it is NULL */ + if (!pipeline_list->pipe_widgets) + goto sink_setup; + + /* + * Add the widget's pipe_widget to the list of pipelines to be triggered if not + * already in the list. This will result in the pipelines getting added in the + * order source to sink. + */ + for (i = 0; i < pipeline_list->count; i++) { + pipe_widget = pipeline_list->pipe_widgets[i]; + if (pipe_widget == swidget->pipe_widget) + break; + } + + if (i == pipeline_list->count) { + pipeline_list->count++; + pipeline_list->pipe_widgets[i] = swidget->pipe_widget; + } }
+sink_setup: snd_soc_dapm_widget_for_each_sink_path(widget, p) { if (!p->walking) { if (!widget_in_list(list, p->sink)) @@ -421,11 +449,11 @@ static int sof_set_up_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_d
p->walking = true;
- ret = sof_set_up_widgets_in_path(sdev, p->sink, dir, list); + ret = sof_set_up_widgets_in_path(sdev, p->sink, dir, spcm); p->walking = false; if (ret < 0) { - if (widget->dobj.private) - sof_widget_free(sdev, widget->dobj.private); + if (swidget) + sof_widget_free(sdev, swidget); return ret; } } @@ -435,16 +463,20 @@ static int sof_set_up_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_d }
static int -sof_walk_widgets_in_order(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget_list *list, +sof_walk_widgets_in_order(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, struct snd_pcm_hw_params *fe_params, struct snd_sof_platform_stream_params *platform_params, int dir, enum sof_widget_op op) { + struct snd_soc_dapm_widget_list *list = spcm->stream[dir].list; struct snd_soc_dapm_widget *widget; char *str; int ret = 0; int i;
+ if (!list) + return 0; + for_each_dapm_widgets(list, i, widget) { /* starting widget for playback is AIF type */ if (dir == SNDRV_PCM_STREAM_PLAYBACK && widget->id != snd_soc_dapm_aif_in) @@ -456,11 +488,11 @@ sof_walk_widgets_in_order(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget_l
switch (op) { case SOF_WIDGET_SETUP: - ret = sof_set_up_widgets_in_path(sdev, widget, dir, list); + ret = sof_set_up_widgets_in_path(sdev, widget, dir, spcm); str = "set up"; break; case SOF_WIDGET_FREE: - ret = sof_free_widgets_in_path(sdev, widget, dir, list); + ret = sof_free_widgets_in_path(sdev, widget, dir, spcm); str = "free"; break; case SOF_WIDGET_PREPARE: @@ -514,16 +546,16 @@ int sof_widget_list_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, * Prepare widgets for set up. The prepare step is used to allocate memory, assign * instance ID and pick the widget configuration based on the runtime PCM params. */ - ret = sof_walk_widgets_in_order(sdev, list, fe_params, platform_params, + ret = sof_walk_widgets_in_order(sdev, spcm, fe_params, platform_params, dir, SOF_WIDGET_PREPARE); if (ret < 0) return ret;
/* Set up is used to send the IPC to the DSP to create the widget */ - ret = sof_walk_widgets_in_order(sdev, list, fe_params, platform_params, + ret = sof_walk_widgets_in_order(sdev, spcm, fe_params, platform_params, dir, SOF_WIDGET_SETUP); if (ret < 0) { - ret = sof_walk_widgets_in_order(sdev, list, fe_params, platform_params, + ret = sof_walk_widgets_in_order(sdev, spcm, fe_params, platform_params, dir, SOF_WIDGET_UNPREPARE); return ret; } @@ -567,15 +599,16 @@ int sof_widget_list_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, return 0;
widget_free: - sof_walk_widgets_in_order(sdev, list, fe_params, platform_params, dir, + sof_walk_widgets_in_order(sdev, spcm, fe_params, platform_params, dir, SOF_WIDGET_FREE); - sof_walk_widgets_in_order(sdev, list, NULL, NULL, dir, SOF_WIDGET_UNPREPARE); + sof_walk_widgets_in_order(sdev, spcm, NULL, NULL, dir, SOF_WIDGET_UNPREPARE);
return ret; }
int sof_widget_list_free(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, int dir) { + struct snd_sof_pcm_stream_pipeline_list *pipeline_list = &spcm->stream[dir].pipeline_list; struct snd_soc_dapm_widget_list *list = spcm->stream[dir].list; int ret;
@@ -584,14 +617,16 @@ int sof_widget_list_free(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, int return 0;
/* send IPC to free widget in the DSP */ - ret = sof_walk_widgets_in_order(sdev, list, NULL, NULL, dir, SOF_WIDGET_FREE); + ret = sof_walk_widgets_in_order(sdev, spcm, NULL, NULL, dir, SOF_WIDGET_FREE);
/* unprepare the widget */ - sof_walk_widgets_in_order(sdev, list, NULL, NULL, dir, SOF_WIDGET_UNPREPARE); + sof_walk_widgets_in_order(sdev, spcm, NULL, NULL, dir, SOF_WIDGET_UNPREPARE);
snd_soc_dapm_dai_free_widgets(&list); spcm->stream[dir].list = NULL;
+ pipeline_list->count = 0; + return ret; }
From: Ranjani Sridharan ranjani.sridharan@linux.intel.com
Use the list of pipelines in the PCM stream's pipeline info to trigger the pipelines in the right order. Add a helper for triggering pipelines in batch mode that will be used to trigger multiple pipelines at the same time.
Signed-off-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com Reviewed-by: Libin Yang libin.yang@intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Reviewed-by: Péter Ujfalusi peter.ujfalusi@linux.intel.com Signed-off-by: Peter Ujfalusi peter.ujfalusi@linux.intel.com --- include/sound/sof/ipc4/header.h | 3 + sound/soc/sof/ipc4-pcm.c | 137 ++++++++++++++++++++++++-------- sound/soc/sof/ipc4-topology.h | 10 +++ 3 files changed, 115 insertions(+), 35 deletions(-)
diff --git a/include/sound/sof/ipc4/header.h b/include/sound/sof/ipc4/header.h index 622193be7ac4..d31349bf011d 100644 --- a/include/sound/sof/ipc4/header.h +++ b/include/sound/sof/ipc4/header.h @@ -185,6 +185,9 @@ enum sof_ipc4_pipeline_state { #define SOF_IPC4_GLB_PIPE_STATE_MASK GENMASK(15, 0) #define SOF_IPC4_GLB_PIPE_STATE(x) ((x) << SOF_IPC4_GLB_PIPE_STATE_SHIFT)
+/* pipeline set state IPC msg extension */ +#define SOF_IPC4_GLB_PIPE_STATE_EXT_MULTI BIT(0) + /* load library ipc msg */ #define SOF_IPC4_GLB_LOAD_LIBRARY_LIB_ID_SHIFT 16 #define SOF_IPC4_GLB_LOAD_LIBRARY_LIB_ID(x) ((x) << SOF_IPC4_GLB_LOAD_LIBRARY_LIB_ID_SHIFT) diff --git a/sound/soc/sof/ipc4-pcm.c b/sound/soc/sof/ipc4-pcm.c index db9d0adb2717..a5482185cd6c 100644 --- a/sound/soc/sof/ipc4-pcm.c +++ b/sound/soc/sof/ipc4-pcm.c @@ -13,6 +13,33 @@ #include "ipc4-priv.h" #include "ipc4-topology.h"
+static int sof_ipc4_set_multi_pipeline_state(struct snd_sof_dev *sdev, u32 state, + struct ipc4_pipeline_set_state_data *data) +{ + struct sof_ipc4_msg msg = {{ 0 }}; + u32 primary, ipc_size; + + /* trigger a single pipeline */ + if (data->count == 1) + return sof_ipc4_set_pipeline_state(sdev, data->pipeline_ids[0], state); + + primary = state; + primary |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_GLB_SET_PIPELINE_STATE); + primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST); + primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_FW_GEN_MSG); + msg.primary = primary; + + /* trigger multiple pipelines with a single IPC */ + msg.extension = SOF_IPC4_GLB_PIPE_STATE_EXT_MULTI; + + /* ipc_size includes the count and the pipeline IDs for the number of pipelines */ + ipc_size = sizeof(u32) * (data->count + 1); + msg.data_size = ipc_size; + msg.data_ptr = data; + + return sof_ipc_tx_message(sdev->ipc, &msg, ipc_size, NULL, 0); +} + int sof_ipc4_set_pipeline_state(struct snd_sof_dev *sdev, u32 id, u32 state) { struct sof_ipc4_msg msg = {{ 0 }}; @@ -37,60 +64,100 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, { struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); - struct snd_sof_widget *pipeline_widget; - struct snd_soc_dapm_widget_list *list; - struct snd_soc_dapm_widget *widget; + struct snd_sof_pcm_stream_pipeline_list *pipeline_list; + struct ipc4_pipeline_set_state_data *data; + struct snd_sof_widget *pipe_widget; struct sof_ipc4_pipeline *pipeline; - struct snd_sof_widget *swidget; struct snd_sof_pcm *spcm; - int ret = 0; - int num_widgets; + int ret; + int i, j;
spcm = snd_sof_find_spcm_dai(component, rtd); if (!spcm) return -EINVAL;
- list = spcm->stream[substream->stream].list; - - for_each_dapm_widgets(list, num_widgets, widget) { - swidget = widget->dobj.private; + pipeline_list = &spcm->stream[substream->stream].pipeline_list; + + /* nothing to trigger if the list is empty */ + if (!pipeline_list->pipe_widgets) + return 0; + + /* allocate memory for the pipeline data */ + data = kzalloc(struct_size(data, pipeline_ids, pipeline_list->count), GFP_KERNEL); + if (!data) + return -ENOMEM; + + /* + * IPC4 requires pipelines to be triggered in order starting at the sink and + * walking all the way to the source. So traverse the pipeline_list in the reverse order. + * Skip the pipelines that have their skip_during_fe_trigger flag set or if they're already + * in the requested state. If there is a fork in the pipeline, the order of triggering + * between the left/right paths will be indeterministic. But the sink->source trigger order + * sink->source would still be guaranteed for each fork independently. + */ + for (i = pipeline_list->count - 1; i >= 0; i--) { + pipe_widget = pipeline_list->pipe_widgets[i]; + pipeline = pipe_widget->private; + if (pipeline->state != state && !pipeline->skip_during_fe_trigger) + data->pipeline_ids[data->count++] = pipe_widget->instance_id; + }
- if (!swidget) - continue; + /* return if all pipelines are in the requested state already */ + if (!data->count) { + kfree(data); + return 0; + }
- pipeline_widget = swidget->pipe_widget; - pipeline = (struct sof_ipc4_pipeline *)pipeline_widget->private; + /* + * Pause all pipelines. This could result in an extra IPC to pause all pipelines even if + * they are already paused. But it helps keep the logic simpler and the firmware handles + * the repeated pause gracefully. This can be optimized in the future if needed. + */ + ret = sof_ipc4_set_multi_pipeline_state(sdev, SOF_IPC4_PIPE_PAUSED, data); + if (ret < 0) { + dev_err(sdev->dev, "failed to pause all pipelines\n"); + goto free; + }
- if (pipeline->state == state || pipeline->skip_during_fe_trigger) - continue; + /* update PAUSED state for all pipelines that were just triggered */ + for (i = 0; i < data->count; i++) { + for (j = 0; j < pipeline_list->count; j++) { + pipe_widget = pipeline_list->pipe_widgets[j]; + pipeline = pipe_widget->private;
- /* first set the pipeline to PAUSED state */ - if (pipeline->state != SOF_IPC4_PIPE_PAUSED) { - ret = sof_ipc4_set_pipeline_state(sdev, pipeline_widget->instance_id, - SOF_IPC4_PIPE_PAUSED); - if (ret < 0) { - dev_err(sdev->dev, "failed to pause pipeline %d\n", - swidget->pipeline_id); - return ret; + if (data->pipeline_ids[i] == pipe_widget->instance_id) { + pipeline->state = SOF_IPC4_PIPE_PAUSED; + break; } } + }
- pipeline->state = SOF_IPC4_PIPE_PAUSED; + /* return if this is the final state */ + if (state == SOF_IPC4_PIPE_PAUSED) + goto free;
- if (pipeline->state == state) - continue; + /* else set the final state in the DSP */ + ret = sof_ipc4_set_multi_pipeline_state(sdev, state, data); + if (ret < 0) { + dev_err(sdev->dev, "failed to set final state %d for all pipelines\n", state); + goto free; + }
- /* then set the final state */ - ret = sof_ipc4_set_pipeline_state(sdev, pipeline_widget->instance_id, state); - if (ret < 0) { - dev_err(sdev->dev, "failed to set state %d for pipeline %d\n", - state, swidget->pipeline_id); - break; - } + /* update final state for all pipelines that were just triggered */ + for (i = 0; i < data->count; i++) { + for (j = 0; j < pipeline_list->count; j++) { + pipe_widget = pipeline_list->pipe_widgets[j]; + pipeline = pipe_widget->private;
- pipeline->state = state; + if (data->pipeline_ids[i] == pipe_widget->instance_id) { + pipeline->state = state; + break; + } + } }
+free: + kfree(data); return ret; }
diff --git a/sound/soc/sof/ipc4-topology.h b/sound/soc/sof/ipc4-topology.h index 028b5d91b9db..ee5d31e68a77 100644 --- a/sound/soc/sof/ipc4-topology.h +++ b/sound/soc/sof/ipc4-topology.h @@ -84,6 +84,16 @@ struct sof_ipc4_pipeline { bool skip_during_fe_trigger; };
+/** + * struct sof_ipc4_multi_pipeline_data - multi pipeline trigger IPC data + * @count: Number of pipelines to be triggered + * @pipeline_ids: Flexible array of IDs of the pipelines to be triggered + */ +struct ipc4_pipeline_set_state_data { + u32 count; + DECLARE_FLEX_ARRAY(u32, pipeline_ids); +} __packed; + /** * struct sof_ipc4_available_audio_format - Available audio formats * @base_config: Available base config
From: Ranjani Sridharan ranjani.sridharan@linux.intel.com
Introduce struct snd_sof_pipeline to save the information about pipelines including the pipeline widget, their status wrt how many PCM's are using them and whether they are complete or not.
In struct snd_sof_widget, replace pipe_widget with spipe and remove complete. In struct snd_sof_pcm_stream_pipeline_list, replace pipe_widgets with pipelines.
Update all users accordingly.
Signed-off-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Reviewed-by: Rander Wang rander.wang@intel.com Reviewed-by: Bard Liao yung-chuan.liao@linux.intel.com Reviewed-by: Péter Ujfalusi peter.ujfalusi@linux.intel.com Signed-off-by: Peter Ujfalusi peter.ujfalusi@linux.intel.com --- sound/soc/sof/core.c | 1 + sound/soc/sof/intel/hda-dai.c | 2 +- sound/soc/sof/ipc3-topology.c | 9 +++-- sound/soc/sof/ipc4-pcm.c | 23 ++++++----- sound/soc/sof/ipc4-topology.c | 12 +++--- sound/soc/sof/sof-audio.c | 48 +++++++++++++++-------- sound/soc/sof/sof-audio.h | 27 +++++++++---- sound/soc/sof/sof-priv.h | 1 + sound/soc/sof/topology.c | 73 +++++++++++++++++++++++------------ 9 files changed, 126 insertions(+), 70 deletions(-)
diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c index 26a3f7c8c914..7de8673a01ce 100644 --- a/sound/soc/sof/core.c +++ b/sound/soc/sof/core.c @@ -390,6 +390,7 @@ int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data) INIT_LIST_HEAD(&sdev->pcm_list); INIT_LIST_HEAD(&sdev->kcontrol_list); INIT_LIST_HEAD(&sdev->widget_list); + INIT_LIST_HEAD(&sdev->pipeline_list); INIT_LIST_HEAD(&sdev->dai_list); INIT_LIST_HEAD(&sdev->dai_link_list); INIT_LIST_HEAD(&sdev->route_list); diff --git a/sound/soc/sof/intel/hda-dai.c b/sound/soc/sof/intel/hda-dai.c index 98eebb4b07e6..193b3e74820a 100644 --- a/sound/soc/sof/intel/hda-dai.c +++ b/sound/soc/sof/intel/hda-dai.c @@ -468,7 +468,7 @@ static int ipc4_hda_dai_trigger(struct snd_pcm_substream *substream,
w = snd_soc_dai_get_widget(dai, substream->stream); swidget = w->dobj.private; - pipe_widget = swidget->pipe_widget; + pipe_widget = swidget->spipe->pipe_widget; pipeline = pipe_widget->private;
switch (cmd) { diff --git a/sound/soc/sof/ipc3-topology.c b/sound/soc/sof/ipc3-topology.c index 3f52dfb19e01..88b9b9507231 100644 --- a/sound/soc/sof/ipc3-topology.c +++ b/sound/soc/sof/ipc3-topology.c @@ -2233,9 +2233,9 @@ static int sof_ipc3_set_up_all_pipelines(struct snd_sof_dev *sdev, bool verify) return ret; }
- swidget->complete = sof_ipc3_complete_pipeline(sdev, swidget); - if (swidget->complete < 0) - return swidget->complete; + swidget->spipe->complete = sof_ipc3_complete_pipeline(sdev, swidget); + if (swidget->spipe->complete < 0) + return swidget->spipe->complete; break; default: break; @@ -2317,7 +2317,8 @@ static int sof_ipc3_tear_down_all_pipelines(struct snd_sof_dev *sdev, bool verif if (!verify && !swidget->dynamic_pipeline_widget && SOF_FW_VER(v->major, v->minor, v->micro) < SOF_FW_VER(2, 2, 0)) { swidget->use_count = 0; - swidget->complete = 0; + if (swidget->spipe) + swidget->spipe->complete = 0; continue; }
diff --git a/sound/soc/sof/ipc4-pcm.c b/sound/soc/sof/ipc4-pcm.c index a5482185cd6c..17a116e8c47c 100644 --- a/sound/soc/sof/ipc4-pcm.c +++ b/sound/soc/sof/ipc4-pcm.c @@ -68,6 +68,7 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, struct ipc4_pipeline_set_state_data *data; struct snd_sof_widget *pipe_widget; struct sof_ipc4_pipeline *pipeline; + struct snd_sof_pipeline *spipe; struct snd_sof_pcm *spcm; int ret; int i, j; @@ -79,7 +80,7 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, pipeline_list = &spcm->stream[substream->stream].pipeline_list;
/* nothing to trigger if the list is empty */ - if (!pipeline_list->pipe_widgets) + if (!pipeline_list->pipelines) return 0;
/* allocate memory for the pipeline data */ @@ -96,7 +97,8 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, * sink->source would still be guaranteed for each fork independently. */ for (i = pipeline_list->count - 1; i >= 0; i--) { - pipe_widget = pipeline_list->pipe_widgets[i]; + spipe = pipeline_list->pipelines[i]; + pipe_widget = spipe->pipe_widget; pipeline = pipe_widget->private; if (pipeline->state != state && !pipeline->skip_during_fe_trigger) data->pipeline_ids[data->count++] = pipe_widget->instance_id; @@ -122,7 +124,8 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, /* update PAUSED state for all pipelines that were just triggered */ for (i = 0; i < data->count; i++) { for (j = 0; j < pipeline_list->count; j++) { - pipe_widget = pipeline_list->pipe_widgets[j]; + spipe = pipeline_list->pipelines[j]; + pipe_widget = spipe->pipe_widget; pipeline = pipe_widget->private;
if (data->pipeline_ids[i] == pipe_widget->instance_id) { @@ -146,7 +149,8 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, /* update final state for all pipelines that were just triggered */ for (i = 0; i < data->count; i++) { for (j = 0; j < pipeline_list->count; j++) { - pipe_widget = pipeline_list->pipe_widgets[j]; + spipe = pipeline_list->pipelines[j]; + pipe_widget = spipe->pipe_widget; pipeline = pipe_widget->private;
if (data->pipeline_ids[i] == pipe_widget->instance_id) { @@ -274,8 +278,8 @@ static void sof_ipc4_pcm_free(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm
for_each_pcm_streams(stream) { pipeline_list = &spcm->stream[stream].pipeline_list; - kfree(pipeline_list->pipe_widgets); - pipeline_list->pipe_widgets = NULL; + kfree(pipeline_list->pipelines); + pipeline_list->pipelines = NULL; } }
@@ -289,10 +293,9 @@ static int sof_ipc4_pcm_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm pipeline_list = &spcm->stream[stream].pipeline_list;
/* allocate memory for max number of pipeline IDs */ - pipeline_list->pipe_widgets = kcalloc(ipc4_data->max_num_pipelines, - sizeof(struct snd_sof_widget *), - GFP_KERNEL); - if (!pipeline_list->pipe_widgets) { + pipeline_list->pipelines = kcalloc(ipc4_data->max_num_pipelines, + sizeof(struct snd_sof_widget *), GFP_KERNEL); + if (!pipeline_list->pipelines) { sof_ipc4_pcm_free(sdev, spcm); return -ENOMEM; } diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index f3b1a7f81216..2f82b5e02a27 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -855,7 +855,7 @@ sof_ipc4_update_pipeline_mem_usage(struct snd_sof_dev *sdev, struct snd_sof_widg
total = SOF_IPC4_FW_PAGE(task_mem + queue_mem);
- pipe_widget = swidget->pipe_widget; + pipe_widget = swidget->spipe->pipe_widget; pipeline = pipe_widget->private; pipeline->mem_usage += total; } @@ -969,7 +969,7 @@ static void sof_ipc4_unprepare_copier_module(struct snd_sof_widget *swidget) struct sof_ipc4_pipeline *pipeline;
/* reset pipeline memory usage */ - pipe_widget = swidget->pipe_widget; + pipe_widget = swidget->spipe->pipe_widget; pipeline = pipe_widget->private; pipeline->mem_usage = 0;
@@ -1136,7 +1136,7 @@ sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, struct snd_sof_widget *pipe_widget; struct sof_ipc4_pipeline *pipeline;
- pipe_widget = swidget->pipe_widget; + pipe_widget = swidget->spipe->pipe_widget; pipeline = pipe_widget->private; ipc4_copier = (struct sof_ipc4_copier *)swidget->private; gtw_attr = ipc4_copier->gtw_attr; @@ -1495,7 +1495,7 @@ static int sof_ipc4_control_setup(struct snd_sof_dev *sdev, struct snd_sof_contr
static int sof_ipc4_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) { - struct snd_sof_widget *pipe_widget = swidget->pipe_widget; + struct snd_sof_widget *pipe_widget = swidget->spipe->pipe_widget; struct sof_ipc4_fw_data *ipc4_data = sdev->private; struct sof_ipc4_pipeline *pipeline; struct sof_ipc4_msg *msg; @@ -1815,7 +1815,7 @@ static int sof_ipc4_route_free(struct snd_sof_dev *sdev, struct snd_sof_route *s * routes belonging to the same pipeline will be disconnected by the FW when the pipeline * is freed. So avoid sending this IPC which will be ignored by the FW anyway. */ - if (src_widget->pipe_widget == sink_widget->pipe_widget) + if (src_widget->spipe->pipe_widget == sink_widget->spipe->pipe_widget) goto out;
header = src_fw_module->man4_module_entry.id; @@ -1846,7 +1846,7 @@ static int sof_ipc4_route_free(struct snd_sof_dev *sdev, struct snd_sof_route *s static int sof_ipc4_dai_config(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget, unsigned int flags, struct snd_sof_dai_config_data *data) { - struct snd_sof_widget *pipe_widget = swidget->pipe_widget; + struct snd_sof_widget *pipe_widget = swidget->spipe->pipe_widget; struct sof_ipc4_pipeline *pipeline = pipe_widget->private; struct snd_sof_dai *dai = swidget->private; struct sof_ipc4_gtw_attributes *gtw_attr; diff --git a/sound/soc/sof/sof-audio.c b/sound/soc/sof/sof-audio.c index e6796c59e04b..158f7b03fbc2 100644 --- a/sound/soc/sof/sof-audio.c +++ b/sound/soc/sof/sof-audio.c @@ -31,6 +31,7 @@ static void sof_reset_route_setup_status(struct snd_sof_dev *sdev, struct snd_so int sof_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) { const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg); + struct snd_sof_widget *pipe_widget; int err = 0; int ret;
@@ -43,6 +44,8 @@ int sof_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) if (--swidget->use_count) return 0;
+ pipe_widget = swidget->spipe->pipe_widget; + /* reset route setup status for all routes that contain this widget */ sof_reset_route_setup_status(sdev, swidget);
@@ -67,12 +70,15 @@ int sof_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) * skip for static pipelines */ if (swidget->dynamic_pipeline_widget && swidget->id != snd_soc_dapm_scheduler) { - ret = sof_widget_free(sdev, swidget->pipe_widget); + ret = sof_widget_free(sdev, pipe_widget); if (ret < 0 && !err) err = ret; - swidget->pipe_widget->complete = 0; }
+ /* clear pipeline complete */ + if (swidget->id == snd_soc_dapm_scheduler) + swidget->spipe->complete = 0; + if (!err) dev_dbg(sdev->dev, "widget %s freed\n", swidget->widget->name);
@@ -103,14 +109,13 @@ int sof_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) * widget in the pipeline is freed. Skip setting up scheduler widget for static pipelines. */ if (swidget->dynamic_pipeline_widget && swidget->id != snd_soc_dapm_scheduler) { - if (!swidget->pipe_widget) { - dev_err(sdev->dev, "No scheduler widget set for %s\n", - swidget->widget->name); + if (!swidget->spipe || !swidget->spipe->pipe_widget) { + dev_err(sdev->dev, "No pipeline set for %s\n", swidget->widget->name); ret = -EINVAL; goto use_count_dec; }
- ret = sof_widget_setup(sdev, swidget->pipe_widget); + ret = sof_widget_setup(sdev, swidget->spipe->pipe_widget); if (ret < 0) goto use_count_dec; } @@ -159,7 +164,7 @@ int sof_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) snd_sof_dsp_core_put(sdev, swidget->core); pipe_widget_free: if (swidget->id != snd_soc_dapm_scheduler) - sof_widget_free(sdev, swidget->pipe_widget); + sof_widget_free(sdev, swidget->spipe->pipe_widget); use_count_dec: swidget->use_count--; return ret; @@ -409,7 +414,7 @@ static int sof_set_up_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_d struct snd_sof_pcm_stream_pipeline_list *pipeline_list = &spcm->stream[dir].pipeline_list; struct snd_soc_dapm_widget_list *list = spcm->stream[dir].list; struct snd_sof_widget *swidget = widget->dobj.private; - struct snd_sof_widget *pipe_widget; + struct snd_sof_pipeline *spipe; struct snd_soc_dapm_path *p; int ret;
@@ -421,7 +426,7 @@ static int sof_set_up_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_d return ret;
/* skip populating the pipe_widgets array if it is NULL */ - if (!pipeline_list->pipe_widgets) + if (!pipeline_list->pipelines) goto sink_setup;
/* @@ -430,14 +435,14 @@ static int sof_set_up_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_d * order source to sink. */ for (i = 0; i < pipeline_list->count; i++) { - pipe_widget = pipeline_list->pipe_widgets[i]; - if (pipe_widget == swidget->pipe_widget) + spipe = pipeline_list->pipelines[i]; + if (spipe == swidget->spipe) break; }
if (i == pipeline_list->count) { pipeline_list->count++; - pipeline_list->pipe_widgets[i] = swidget->pipe_widget; + pipeline_list->pipelines[i] = swidget->spipe; } }
@@ -572,11 +577,20 @@ int sof_widget_list_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, for_each_dapm_widgets(list, i, widget) { struct snd_sof_widget *swidget = widget->dobj.private; struct snd_sof_widget *pipe_widget; + struct snd_sof_pipeline *spipe;
if (!swidget) continue;
- pipe_widget = swidget->pipe_widget; + spipe = swidget->spipe; + if (!spipe) { + dev_err(sdev->dev, "no pipeline found for %s\n", + swidget->widget->name); + ret = -EINVAL; + goto widget_free; + } + + pipe_widget = spipe->pipe_widget; if (!pipe_widget) { dev_err(sdev->dev, "error: no pipeline widget found for %s\n", swidget->widget->name); @@ -584,13 +598,13 @@ int sof_widget_list_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, goto widget_free; }
- if (pipe_widget->complete) + if (spipe->complete) continue;
if (tplg_ops && tplg_ops->pipeline_complete) { - pipe_widget->complete = tplg_ops->pipeline_complete(sdev, pipe_widget); - if (pipe_widget->complete < 0) { - ret = pipe_widget->complete; + spipe->complete = tplg_ops->pipeline_complete(sdev, pipe_widget); + if (spipe->complete < 0) { + ret = spipe->complete; goto widget_free; } } diff --git a/sound/soc/sof/sof-audio.h b/sound/soc/sof/sof-audio.h index bb5c61dd9b1e..f1bbd1adc8b6 100644 --- a/sound/soc/sof/sof-audio.h +++ b/sound/soc/sof/sof-audio.h @@ -288,11 +288,11 @@ struct sof_token_info { /** * struct snd_sof_pcm_stream_pipeline_list - List of pipelines associated with a PCM stream * @count: number of pipeline widgets in the @pipe_widgets array - * @pipe_widgets: array of pipeline widgets + * @pipelines: array of pipelines */ struct snd_sof_pcm_stream_pipeline_list { u32 count; - struct snd_sof_widget **pipe_widgets; + struct snd_sof_pipeline **pipelines; };
/* PCM stream, mapped to FW component */ @@ -382,11 +382,6 @@ struct snd_sof_widget { struct snd_soc_component *scomp; int comp_id; int pipeline_id; - /* - * complete flag is used to indicate that pipeline set up is complete for scheduler type - * widgets. It is unused for all other widget types. - */ - int complete; /* * the prepared flag is used to indicate that a widget has been prepared for getting set * up in the DSP. @@ -413,7 +408,7 @@ struct snd_sof_widget {
struct snd_soc_dapm_widget *widget; struct list_head list; /* list in sdev widget list */ - struct snd_sof_widget *pipe_widget; + struct snd_sof_pipeline *spipe; void *module_info;
const guid_t uuid; @@ -451,6 +446,22 @@ struct snd_sof_widget { void *private; /* core does not touch this */ };
+/** struct snd_sof_pipeline - ASoC SOF pipeline + * @pipe_widget: Pointer to the pipeline widget + * @started_count: Count of number of PCM's that have started this pipeline + * @paused_count: Count of number of PCM's that have started and have currently paused this + pipeline + * @complete: flag used to indicate that pipeline set up is complete. + * @list: List item in sdev pipeline_list + */ +struct snd_sof_pipeline { + struct snd_sof_widget *pipe_widget; + int started_count; + int paused_count; + int complete; + struct list_head list; +}; + /* ASoC SOF DAPM route */ struct snd_sof_route { struct snd_soc_component *scomp; diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index 86fc5c6a9c39..208a30ff3db9 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -578,6 +578,7 @@ struct snd_sof_dev { struct list_head pcm_list; struct list_head kcontrol_list; struct list_head widget_list; + struct list_head pipeline_list; struct list_head dai_list; struct list_head dai_link_list; struct list_head route_list; diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c index 51f6fed45ae7..33ca3067262b 100644 --- a/sound/soc/sof/topology.c +++ b/sound/soc/sof/topology.c @@ -1402,7 +1402,6 @@ static int sof_widget_ready(struct snd_soc_component *scomp, int index, swidget->scomp = scomp; swidget->widget = w; swidget->comp_id = sdev->next_comp_id++; - swidget->complete = 0; swidget->id = w->id; swidget->pipeline_id = index; swidget->private = NULL; @@ -1553,6 +1552,23 @@ static int sof_widget_ready(struct snd_soc_component *scomp, int index, } }
+ /* create and add pipeline for scheduler type widgets */ + if (w->id == snd_soc_dapm_scheduler) { + struct snd_sof_pipeline *spipe; + + spipe = kzalloc(sizeof(*spipe), GFP_KERNEL); + if (!spipe) { + kfree(swidget->private); + kfree(swidget->tuples); + kfree(swidget); + return -ENOMEM; + } + + spipe->pipe_widget = swidget; + swidget->spipe = spipe; + list_add(&spipe->list, &sdev->pipeline_list); + } + w->dobj.private = swidget; list_add(&swidget->list, &sdev->widget_list); return ret; @@ -1608,6 +1624,15 @@ static int sof_widget_unload(struct snd_soc_component *scomp, sof_disconnect_dai_widget(scomp, widget);
break; + case snd_soc_dapm_scheduler: + { + struct snd_sof_pipeline *spipe = swidget->spipe; + + list_del(&spipe->list); + kfree(spipe); + swidget->spipe = NULL; + break; + } default: break; } @@ -2082,18 +2107,19 @@ static int sof_route_load(struct snd_soc_component *scomp, int index, }
/** - * sof_set_pipe_widget - Set pipe_widget for a component + * sof_set_widget_pipeline - Set pipeline for a component * @sdev: pointer to struct snd_sof_dev - * @pipe_widget: pointer to struct snd_sof_widget of type snd_soc_dapm_scheduler + * @spipe: pointer to struct snd_sof_pipeline * @swidget: pointer to struct snd_sof_widget that has the same pipeline ID as @pipe_widget * * Return: 0 if successful, -EINVAL on error. * The function checks if @swidget is associated with any volatile controls. If so, setting * the dynamic_pipeline_widget is disallowed. */ -static int sof_set_pipe_widget(struct snd_sof_dev *sdev, struct snd_sof_widget *pipe_widget, - struct snd_sof_widget *swidget) +static int sof_set_widget_pipeline(struct snd_sof_dev *sdev, struct snd_sof_pipeline *spipe, + struct snd_sof_widget *swidget) { + struct snd_sof_widget *pipe_widget = spipe->pipe_widget; struct snd_sof_control *scontrol;
if (pipe_widget->dynamic_pipeline_widget) { @@ -2108,8 +2134,8 @@ static int sof_set_pipe_widget(struct snd_sof_dev *sdev, struct snd_sof_widget * } }
- /* set the pipe_widget and apply the dynamic_pipeline_widget_flag */ - swidget->pipe_widget = pipe_widget; + /* set the pipeline and apply the dynamic_pipeline_widget_flag */ + swidget->spipe = spipe; swidget->dynamic_pipeline_widget = pipe_widget->dynamic_pipeline_widget;
return 0; @@ -2123,6 +2149,7 @@ static int sof_complete(struct snd_soc_component *scomp) struct snd_sof_widget *swidget, *comp_swidget; const struct sof_ipc_tplg_widget_ops *widget_ops; struct snd_sof_control *scontrol; + struct snd_sof_pipeline *spipe; int ret;
widget_ops = tplg_ops ? tplg_ops->widget : NULL; @@ -2155,23 +2182,21 @@ static int sof_complete(struct snd_soc_component *scomp) }
/* set the pipe_widget and apply the dynamic_pipeline_widget_flag */ - list_for_each_entry(swidget, &sdev->widget_list, list) { - switch (swidget->id) { - case snd_soc_dapm_scheduler: - /* - * Apply the dynamic_pipeline_widget flag and set the pipe_widget field - * for all widgets that have the same pipeline ID as the scheduler widget - */ - list_for_each_entry(comp_swidget, &sdev->widget_list, list) - if (comp_swidget->pipeline_id == swidget->pipeline_id) { - ret = sof_set_pipe_widget(sdev, swidget, comp_swidget); - if (ret < 0) - return ret; - } - break; - default: - break; - } + list_for_each_entry(spipe, &sdev->pipeline_list, list) { + struct snd_sof_widget *pipe_widget = spipe->pipe_widget; + + /* + * Apply the dynamic_pipeline_widget flag and set the pipe_widget field + * for all widgets that have the same pipeline ID as the scheduler widget. + * Skip the scheduler widgets as they have their pipeline set during widget_ready + */ + list_for_each_entry(comp_swidget, &sdev->widget_list, list) + if (comp_swidget->widget->id != snd_soc_dapm_scheduler && + comp_swidget->pipeline_id == pipe_widget->pipeline_id) { + ret = sof_set_widget_pipeline(sdev, spipe, comp_swidget); + if (ret < 0) + return ret; + } }
/* verify topology components loading including dynamic pipelines */
From: Ranjani Sridharan ranjani.sridharan@linux.intel.com
For more clarity, rename the struct ipc4_pipeline_set_state_data variable to trigger_list instead of data. No functionality change.
Signed-off-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Reviewed-by: Rander Wang rander.wang@intel.com Reviewed-by: Bard Liao yung-chuan.liao@linux.intel.com Reviewed-by: Péter Ujfalusi peter.ujfalusi@linux.intel.com Signed-off-by: Peter Ujfalusi peter.ujfalusi@linux.intel.com --- sound/soc/sof/ipc4-pcm.c | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-)
diff --git a/sound/soc/sof/ipc4-pcm.c b/sound/soc/sof/ipc4-pcm.c index 17a116e8c47c..284e402709be 100644 --- a/sound/soc/sof/ipc4-pcm.c +++ b/sound/soc/sof/ipc4-pcm.c @@ -14,14 +14,14 @@ #include "ipc4-topology.h"
static int sof_ipc4_set_multi_pipeline_state(struct snd_sof_dev *sdev, u32 state, - struct ipc4_pipeline_set_state_data *data) + struct ipc4_pipeline_set_state_data *trigger_list) { struct sof_ipc4_msg msg = {{ 0 }}; u32 primary, ipc_size;
/* trigger a single pipeline */ - if (data->count == 1) - return sof_ipc4_set_pipeline_state(sdev, data->pipeline_ids[0], state); + if (trigger_list->count == 1) + return sof_ipc4_set_pipeline_state(sdev, trigger_list->pipeline_ids[0], state);
primary = state; primary |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_GLB_SET_PIPELINE_STATE); @@ -33,9 +33,9 @@ static int sof_ipc4_set_multi_pipeline_state(struct snd_sof_dev *sdev, u32 state msg.extension = SOF_IPC4_GLB_PIPE_STATE_EXT_MULTI;
/* ipc_size includes the count and the pipeline IDs for the number of pipelines */ - ipc_size = sizeof(u32) * (data->count + 1); + ipc_size = sizeof(u32) * (trigger_list->count + 1); msg.data_size = ipc_size; - msg.data_ptr = data; + msg.data_ptr = trigger_list;
return sof_ipc_tx_message(sdev->ipc, &msg, ipc_size, NULL, 0); } @@ -65,7 +65,7 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); struct snd_sof_pcm_stream_pipeline_list *pipeline_list; - struct ipc4_pipeline_set_state_data *data; + struct ipc4_pipeline_set_state_data *trigger_list; struct snd_sof_widget *pipe_widget; struct sof_ipc4_pipeline *pipeline; struct snd_sof_pipeline *spipe; @@ -84,8 +84,9 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, return 0;
/* allocate memory for the pipeline data */ - data = kzalloc(struct_size(data, pipeline_ids, pipeline_list->count), GFP_KERNEL); - if (!data) + trigger_list = kzalloc(struct_size(trigger_list, pipeline_ids, pipeline_list->count), + GFP_KERNEL); + if (!trigger_list) return -ENOMEM;
/* @@ -101,12 +102,13 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, pipe_widget = spipe->pipe_widget; pipeline = pipe_widget->private; if (pipeline->state != state && !pipeline->skip_during_fe_trigger) - data->pipeline_ids[data->count++] = pipe_widget->instance_id; + trigger_list->pipeline_ids[trigger_list->count++] = + pipe_widget->instance_id; }
/* return if all pipelines are in the requested state already */ - if (!data->count) { - kfree(data); + if (!trigger_list->count) { + kfree(trigger_list); return 0; }
@@ -115,20 +117,20 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, * they are already paused. But it helps keep the logic simpler and the firmware handles * the repeated pause gracefully. This can be optimized in the future if needed. */ - ret = sof_ipc4_set_multi_pipeline_state(sdev, SOF_IPC4_PIPE_PAUSED, data); + ret = sof_ipc4_set_multi_pipeline_state(sdev, SOF_IPC4_PIPE_PAUSED, trigger_list); if (ret < 0) { dev_err(sdev->dev, "failed to pause all pipelines\n"); goto free; }
/* update PAUSED state for all pipelines that were just triggered */ - for (i = 0; i < data->count; i++) { + for (i = 0; i < trigger_list->count; i++) { for (j = 0; j < pipeline_list->count; j++) { spipe = pipeline_list->pipelines[j]; pipe_widget = spipe->pipe_widget; pipeline = pipe_widget->private;
- if (data->pipeline_ids[i] == pipe_widget->instance_id) { + if (trigger_list->pipeline_ids[i] == pipe_widget->instance_id) { pipeline->state = SOF_IPC4_PIPE_PAUSED; break; } @@ -140,20 +142,20 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, goto free;
/* else set the final state in the DSP */ - ret = sof_ipc4_set_multi_pipeline_state(sdev, state, data); + ret = sof_ipc4_set_multi_pipeline_state(sdev, state, trigger_list); if (ret < 0) { dev_err(sdev->dev, "failed to set final state %d for all pipelines\n", state); goto free; }
/* update final state for all pipelines that were just triggered */ - for (i = 0; i < data->count; i++) { + for (i = 0; i < trigger_list->count; i++) { for (j = 0; j < pipeline_list->count; j++) { spipe = pipeline_list->pipelines[j]; pipe_widget = spipe->pipe_widget; pipeline = pipe_widget->private;
- if (data->pipeline_ids[i] == pipe_widget->instance_id) { + if (trigger_list->pipeline_ids[i] == pipe_widget->instance_id) { pipeline->state = state; break; } @@ -161,7 +163,7 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, }
free: - kfree(data); + kfree(trigger_list); return ret; }
From: Ranjani Sridharan ranjani.sridharan@linux.intel.com
Use the started_count and paused_count to implement reference counting when making decisions to start/stop/pause pipelines during the FE DAI trigger. This is necessary to trigger the shared pipelines in the FE DAI trigger properly.
With IPC4, the FE trigger will issue multiple pipeline state changes, and the triggers are propagated downstream to connected pipelines by the SOF driver - not the firmware. This creates a window for race conditions where an FE trigger preempts another one, which results in inconsistent pipeline states and refcounts.
This patch introduces a mutex lock for the pcm trigger that guarantees that IPC4 state and resources are accessed in a serialized manner.
Signed-off-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Reviewed-by: Rander Wang rander.wang@intel.com Reviewed-by: Bard Liao yung-chuan.liao@linux.intel.com Reviewed-by: Péter Ujfalusi peter.ujfalusi@linux.intel.com Signed-off-by: Peter Ujfalusi peter.ujfalusi@linux.intel.com --- sound/soc/sof/ipc4-pcm.c | 228 +++++++++++++++++++++++++++++--------- sound/soc/sof/ipc4-priv.h | 2 + sound/soc/sof/ipc4.c | 2 + 3 files changed, 182 insertions(+), 50 deletions(-)
diff --git a/sound/soc/sof/ipc4-pcm.c b/sound/soc/sof/ipc4-pcm.c index 284e402709be..ababa29d6eac 100644 --- a/sound/soc/sof/ipc4-pcm.c +++ b/sound/soc/sof/ipc4-pcm.c @@ -59,19 +59,152 @@ int sof_ipc4_set_pipeline_state(struct snd_sof_dev *sdev, u32 id, u32 state) } EXPORT_SYMBOL(sof_ipc4_set_pipeline_state);
+static void +sof_ipc4_add_pipeline_to_trigger_list(struct snd_sof_dev *sdev, int state, + struct snd_sof_pipeline *spipe, + struct ipc4_pipeline_set_state_data *trigger_list) +{ + struct snd_sof_widget *pipe_widget = spipe->pipe_widget; + struct sof_ipc4_pipeline *pipeline = pipe_widget->private; + + if (pipeline->skip_during_fe_trigger) + return; + + switch (state) { + case SOF_IPC4_PIPE_RUNNING: + /* + * Trigger pipeline if all PCMs containing it are paused or if it is RUNNING + * for the first time + */ + if (spipe->started_count == spipe->paused_count) + trigger_list->pipeline_ids[trigger_list->count++] = + pipe_widget->instance_id; + break; + case SOF_IPC4_PIPE_RESET: + /* RESET if the pipeline is neither running nor paused */ + if (!spipe->started_count && !spipe->paused_count) + trigger_list->pipeline_ids[trigger_list->count++] = + pipe_widget->instance_id; + break; + case SOF_IPC4_PIPE_PAUSED: + /* Pause the pipeline only when its started_count is 1 more than paused_count */ + if (spipe->paused_count == (spipe->started_count - 1)) + trigger_list->pipeline_ids[trigger_list->count++] = + pipe_widget->instance_id; + break; + default: + break; + } +} + +static void +sof_ipc4_update_pipeline_state(struct snd_sof_dev *sdev, int state, int cmd, + struct snd_sof_pipeline *spipe, + struct ipc4_pipeline_set_state_data *trigger_list) +{ + struct snd_sof_widget *pipe_widget = spipe->pipe_widget; + struct sof_ipc4_pipeline *pipeline = pipe_widget->private; + int i; + + if (pipeline->skip_during_fe_trigger) + return; + + /* set state for pipeline if it was just triggered */ + for (i = 0; i < trigger_list->count; i++) { + if (trigger_list->pipeline_ids[i] == pipe_widget->instance_id) { + pipeline->state = state; + break; + } + } + + switch (state) { + case SOF_IPC4_PIPE_PAUSED: + switch (cmd) { + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + /* + * increment paused_count if the PAUSED is the final state during + * the PAUSE trigger + */ + spipe->paused_count++; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + /* + * decrement started_count if PAUSED is the final state during the + * STOP trigger + */ + spipe->started_count--; + break; + default: + break; + } + break; + case SOF_IPC4_PIPE_RUNNING: + switch (cmd) { + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + /* decrement paused_count for RELEASE */ + spipe->paused_count--; + break; + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + /* increment started_count for START/RESUME */ + spipe->started_count++; + break; + default: + break; + } + break; + default: + break; + } +} + +/* + * The picture below represents the pipeline state machine wrt PCM actions corresponding to the + * triggers and ioctls + * +---------------+ + * | | + * | INIT | + * | | + * +-------+-------+ + * | + * | + * | START + * | + * | + * +----------------+ +------v-------+ +-------------+ + * | | START | | HW_FREE | | + * | RUNNING <-------------+ PAUSED +--------------> + RESET | + * | | PAUSE | | | | + * +------+---------+ RELEASE +---------+----+ +-------------+ + * | ^ + * | | + * | | + * | | + * | PAUSE | + * +---------------------------------+ + * STOP/SUSPEND + * + * Note that during system suspend, the suspend trigger is followed by a hw_free in + * sof_pcm_trigger(). So, the final state during suspend would be RESET. + * Also, since the SOF driver doesn't support full resume, streams would be restarted with the + * prepare ioctl before the START trigger. + */ + static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, - struct snd_pcm_substream *substream, int state) + struct snd_pcm_substream *substream, int state, int cmd) { struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); struct snd_sof_pcm_stream_pipeline_list *pipeline_list; + struct sof_ipc4_fw_data *ipc4_data = sdev->private; struct ipc4_pipeline_set_state_data *trigger_list; - struct snd_sof_widget *pipe_widget; - struct sof_ipc4_pipeline *pipeline; struct snd_sof_pipeline *spipe; struct snd_sof_pcm *spcm; int ret; - int i, j; + int i; + + dev_dbg(sdev->dev, "trigger cmd: %d state: %d\n", cmd, state);
spcm = snd_sof_find_spcm_dai(component, rtd); if (!spcm) @@ -89,33 +222,41 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, if (!trigger_list) return -ENOMEM;
+ mutex_lock(&ipc4_data->trigger_mutex); + /* * IPC4 requires pipelines to be triggered in order starting at the sink and - * walking all the way to the source. So traverse the pipeline_list in the reverse order. - * Skip the pipelines that have their skip_during_fe_trigger flag set or if they're already - * in the requested state. If there is a fork in the pipeline, the order of triggering - * between the left/right paths will be indeterministic. But the sink->source trigger order - * sink->source would still be guaranteed for each fork independently. + * walking all the way to the source. So traverse the pipeline_list in the order + * sink->source when starting PCM's and in the reverse order to pause/stop PCM's. + * Skip the pipelines that have their skip_during_fe_trigger flag set. If there is a fork + * in the pipeline, the order of triggering between the left/right paths will be + * indeterministic. But the sink->source trigger order sink->source would still be + * guaranteed for each fork independently. */ - for (i = pipeline_list->count - 1; i >= 0; i--) { - spipe = pipeline_list->pipelines[i]; - pipe_widget = spipe->pipe_widget; - pipeline = pipe_widget->private; - if (pipeline->state != state && !pipeline->skip_during_fe_trigger) - trigger_list->pipeline_ids[trigger_list->count++] = - pipe_widget->instance_id; - } + if (state == SOF_IPC4_PIPE_RUNNING || state == SOF_IPC4_PIPE_RESET) + for (i = pipeline_list->count - 1; i >= 0; i--) { + spipe = pipeline_list->pipelines[i]; + sof_ipc4_add_pipeline_to_trigger_list(sdev, state, spipe, trigger_list); + } + else + for (i = 0; i < pipeline_list->count; i++) { + spipe = pipeline_list->pipelines[i]; + sof_ipc4_add_pipeline_to_trigger_list(sdev, state, spipe, trigger_list); + }
/* return if all pipelines are in the requested state already */ if (!trigger_list->count) { - kfree(trigger_list); - return 0; + ret = 0; + goto free; }
+ /* no need to pause before reset or before pause release */ + if (state == SOF_IPC4_PIPE_RESET || cmd == SNDRV_PCM_TRIGGER_PAUSE_RELEASE) + goto skip_pause_transition; + /* - * Pause all pipelines. This could result in an extra IPC to pause all pipelines even if - * they are already paused. But it helps keep the logic simpler and the firmware handles - * the repeated pause gracefully. This can be optimized in the future if needed. + * set paused state for pipelines if the final state is PAUSED or when the pipeline + * is set to RUNNING for the first time after the PCM is started. */ ret = sof_ipc4_set_multi_pipeline_state(sdev, SOF_IPC4_PIPE_PAUSED, trigger_list); if (ret < 0) { @@ -123,46 +264,32 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, goto free; }
- /* update PAUSED state for all pipelines that were just triggered */ - for (i = 0; i < trigger_list->count; i++) { - for (j = 0; j < pipeline_list->count; j++) { - spipe = pipeline_list->pipelines[j]; - pipe_widget = spipe->pipe_widget; - pipeline = pipe_widget->private; - - if (trigger_list->pipeline_ids[i] == pipe_widget->instance_id) { - pipeline->state = SOF_IPC4_PIPE_PAUSED; - break; - } - } + /* update PAUSED state for all pipelines just triggered */ + for (i = 0; i < pipeline_list->count ; i++) { + spipe = pipeline_list->pipelines[i]; + sof_ipc4_update_pipeline_state(sdev, SOF_IPC4_PIPE_PAUSED, cmd, spipe, + trigger_list); }
/* return if this is the final state */ if (state == SOF_IPC4_PIPE_PAUSED) goto free; - - /* else set the final state in the DSP */ +skip_pause_transition: + /* else set the RUNNING/RESET state in the DSP */ ret = sof_ipc4_set_multi_pipeline_state(sdev, state, trigger_list); if (ret < 0) { dev_err(sdev->dev, "failed to set final state %d for all pipelines\n", state); goto free; }
- /* update final state for all pipelines that were just triggered */ - for (i = 0; i < trigger_list->count; i++) { - for (j = 0; j < pipeline_list->count; j++) { - spipe = pipeline_list->pipelines[j]; - pipe_widget = spipe->pipe_widget; - pipeline = pipe_widget->private; - - if (trigger_list->pipeline_ids[i] == pipe_widget->instance_id) { - pipeline->state = state; - break; - } - } + /* update RUNNING/RESET state for all pipelines that were just triggered */ + for (i = 0; i < pipeline_list->count; i++) { + spipe = pipeline_list->pipelines[i]; + sof_ipc4_update_pipeline_state(sdev, state, cmd, spipe, trigger_list); }
free: + mutex_unlock(&ipc4_data->trigger_mutex); kfree(trigger_list); return ret; } @@ -192,13 +319,14 @@ static int sof_ipc4_pcm_trigger(struct snd_soc_component *component, }
/* set the pipeline state */ - return sof_ipc4_trigger_pipelines(component, substream, state); + return sof_ipc4_trigger_pipelines(component, substream, state, cmd); }
static int sof_ipc4_pcm_hw_free(struct snd_soc_component *component, struct snd_pcm_substream *substream) { - return sof_ipc4_trigger_pipelines(component, substream, SOF_IPC4_PIPE_RESET); + /* command is not relevant with RESET, so just pass 0 */ + return sof_ipc4_trigger_pipelines(component, substream, SOF_IPC4_PIPE_RESET, 0); }
static void ipc4_ssp_dai_config_pcm_params_match(struct snd_sof_dev *sdev, const char *link_name, diff --git a/sound/soc/sof/ipc4-priv.h b/sound/soc/sof/ipc4-priv.h index fc9efdce67e0..0c0d48376045 100644 --- a/sound/soc/sof/ipc4-priv.h +++ b/sound/soc/sof/ipc4-priv.h @@ -70,6 +70,7 @@ struct sof_ipc4_fw_library { * base firmware * * @load_library: Callback function for platform dependent library loading + * @trigger_mutex: Mutex to protect pipeline triggers, ref counts and states */ struct sof_ipc4_fw_data { u32 manifest_fw_hdr_offset; @@ -82,6 +83,7 @@ struct sof_ipc4_fw_data {
int (*load_library)(struct snd_sof_dev *sdev, struct sof_ipc4_fw_library *fw_lib, bool reload); + struct mutex trigger_mutex; /* protect pipeline triggers, ref counts and states */ };
extern const struct sof_ipc_fw_loader_ops ipc4_loader_ops; diff --git a/sound/soc/sof/ipc4.c b/sound/soc/sof/ipc4.c index 74cd7e956019..fb4760ae593f 100644 --- a/sound/soc/sof/ipc4.c +++ b/sound/soc/sof/ipc4.c @@ -662,6 +662,8 @@ static int sof_ipc4_init(struct snd_sof_dev *sdev) { struct sof_ipc4_fw_data *ipc4_data = sdev->private;
+ mutex_init(&ipc4_data->trigger_mutex); + xa_init_flags(&ipc4_data->fw_lib_xa, XA_FLAGS_ALLOC);
return 0;
From: Ranjani Sridharan ranjani.sridharan@linux.intel.com
When starting/stopping multiple streams in parallel, pipeline triggers and pipeline frees can get interleaved. So use the same mutex used for pipeline trigger to protect the pipeline frees as well. Rename the trigger_mutex to pipeline_state_mutex for more clarity.
Signed-off-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com Reviewed-by: Péter Ujfalusi peter.ujfalusi@linux.intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Signed-off-by: Peter Ujfalusi peter.ujfalusi@linux.intel.com --- sound/soc/sof/ipc4-pcm.c | 4 ++-- sound/soc/sof/ipc4-priv.h | 4 ++-- sound/soc/sof/ipc4-topology.c | 5 +++++ sound/soc/sof/ipc4.c | 2 +- 4 files changed, 10 insertions(+), 5 deletions(-)
diff --git a/sound/soc/sof/ipc4-pcm.c b/sound/soc/sof/ipc4-pcm.c index ababa29d6eac..2d89d3708ed0 100644 --- a/sound/soc/sof/ipc4-pcm.c +++ b/sound/soc/sof/ipc4-pcm.c @@ -222,7 +222,7 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, if (!trigger_list) return -ENOMEM;
- mutex_lock(&ipc4_data->trigger_mutex); + mutex_lock(&ipc4_data->pipeline_state_mutex);
/* * IPC4 requires pipelines to be triggered in order starting at the sink and @@ -289,7 +289,7 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, }
free: - mutex_unlock(&ipc4_data->trigger_mutex); + mutex_unlock(&ipc4_data->pipeline_state_mutex); kfree(trigger_list); return ret; } diff --git a/sound/soc/sof/ipc4-priv.h b/sound/soc/sof/ipc4-priv.h index 0c0d48376045..38bb3d7df42e 100644 --- a/sound/soc/sof/ipc4-priv.h +++ b/sound/soc/sof/ipc4-priv.h @@ -70,7 +70,7 @@ struct sof_ipc4_fw_library { * base firmware * * @load_library: Callback function for platform dependent library loading - * @trigger_mutex: Mutex to protect pipeline triggers, ref counts and states + * @pipeline_state_mutex: Mutex to protect pipeline triggers, ref counts, states and deletion */ struct sof_ipc4_fw_data { u32 manifest_fw_hdr_offset; @@ -83,7 +83,7 @@ struct sof_ipc4_fw_data {
int (*load_library)(struct snd_sof_dev *sdev, struct sof_ipc4_fw_library *fw_lib, bool reload); - struct mutex trigger_mutex; /* protect pipeline triggers, ref counts and states */ + struct mutex pipeline_state_mutex; /* protect pipeline triggers, ref counts and states */ };
extern const struct sof_ipc_fw_loader_ops ipc4_loader_ops; diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index 2f82b5e02a27..43340c8917b7 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -1625,8 +1625,11 @@ static int sof_ipc4_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget static int sof_ipc4_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) { struct sof_ipc4_fw_module *fw_module = swidget->module_info; + struct sof_ipc4_fw_data *ipc4_data = sdev->private; int ret = 0;
+ mutex_lock(&ipc4_data->pipeline_state_mutex); + /* freeing a pipeline frees all the widgets associated with it */ if (swidget->id == snd_soc_dapm_scheduler) { struct sof_ipc4_pipeline *pipeline = swidget->private; @@ -1652,6 +1655,8 @@ static int sof_ipc4_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget ida_free(&fw_module->m_ida, swidget->instance_id); }
+ mutex_unlock(&ipc4_data->pipeline_state_mutex); + return ret; }
diff --git a/sound/soc/sof/ipc4.c b/sound/soc/sof/ipc4.c index fb4760ae593f..35c9f3913d9a 100644 --- a/sound/soc/sof/ipc4.c +++ b/sound/soc/sof/ipc4.c @@ -662,7 +662,7 @@ static int sof_ipc4_init(struct snd_sof_dev *sdev) { struct sof_ipc4_fw_data *ipc4_data = sdev->private;
- mutex_init(&ipc4_data->trigger_mutex); + mutex_init(&ipc4_data->pipeline_state_mutex);
xa_init_flags(&ipc4_data->fw_lib_xa, XA_FLAGS_ALLOC);
The sof_widget_free() on the error path will decrement the use count and if we jump to widget_free: then the use_count will be decremented by two, which is not correct as we only incremented once with 1.
Signed-off-by: Peter Ujfalusi peter.ujfalusi@linux.intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Reviewed-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com --- sound/soc/sof/sof-audio.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/sound/soc/sof/sof-audio.c b/sound/soc/sof/sof-audio.c index 158f7b03fbc2..14b4b949d081 100644 --- a/sound/soc/sof/sof-audio.c +++ b/sound/soc/sof/sof-audio.c @@ -89,6 +89,7 @@ EXPORT_SYMBOL(sof_widget_free); int sof_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) { const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg); + bool use_count_decremented = false; int ret;
/* skip if there is no private data */ @@ -160,13 +161,16 @@ int sof_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) widget_free: /* widget use_count and core ref_count will both be decremented by sof_widget_free() */ sof_widget_free(sdev, swidget); + use_count_decremented = true; core_put: snd_sof_dsp_core_put(sdev, swidget->core); pipe_widget_free: if (swidget->id != snd_soc_dapm_scheduler) sof_widget_free(sdev, swidget->spipe->pipe_widget); use_count_dec: - swidget->use_count--; + if (!use_count_decremented) + swidget->use_count--; + return ret; } EXPORT_SYMBOL(sof_widget_setup);
The use_count of the swidget is protect by ALSA core PCM locking with the exception when an associated kcontrol is changed.
It has been observed that a rightly timed kcontrol access during stream stop can result of an attempt to send a control update to a widget which has been freed up between the check of the use_count and the message sending.
We need to protect the entire sof_widget_setup() and sof_widget_free() execution to make it safe to rely on the use_count. Move the code under an _unlocked() function and use a mutex to protect the execution of the functions for concurrency. On the control path we need to use the lock only for the kcontrol access, the widget_kcontrol_setup() op is called with the lock already held.
Reported-by: Guennadi Liakhovetski guennadi.liakhovetski@linux.intel.com Signed-off-by: Peter Ujfalusi peter.ujfalusi@linux.intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Reviewed-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com --- sound/soc/sof/ipc3-control.c | 46 +++++++++++++++++++++++------------ sound/soc/sof/ipc3-topology.c | 2 ++ sound/soc/sof/ipc4-control.c | 33 +++++++++++++++++-------- sound/soc/sof/sof-audio.c | 36 ++++++++++++++++++++++----- sound/soc/sof/sof-audio.h | 11 ++++++++- sound/soc/sof/topology.c | 2 ++ 6 files changed, 97 insertions(+), 33 deletions(-)
diff --git a/sound/soc/sof/ipc3-control.c b/sound/soc/sof/ipc3-control.c index 3fdc0d854e65..217ac5501a98 100644 --- a/sound/soc/sof/ipc3-control.c +++ b/sound/soc/sof/ipc3-control.c @@ -12,7 +12,8 @@ #include "ipc3-priv.h"
/* IPC set()/get() for kcontrols. */ -static int sof_ipc3_set_get_kcontrol_data(struct snd_sof_control *scontrol, bool set) +static int sof_ipc3_set_get_kcontrol_data(struct snd_sof_control *scontrol, + bool set, bool lock) { struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scontrol->scomp); struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data; @@ -21,6 +22,7 @@ static int sof_ipc3_set_get_kcontrol_data(struct snd_sof_control *scontrol, bool struct snd_sof_widget *swidget; bool widget_found = false; u32 ipc_cmd, msg_bytes; + int ret = 0;
list_for_each_entry(swidget, &sdev->widget_list, list) { if (swidget->comp_id == scontrol->comp_id) { @@ -35,13 +37,18 @@ static int sof_ipc3_set_get_kcontrol_data(struct snd_sof_control *scontrol, bool return -EINVAL; }
+ if (lock) + mutex_lock(&swidget->setup_mutex); + else + lockdep_assert_held(&swidget->setup_mutex); + /* - * Volatile controls should always be part of static pipelines and the widget use_count - * would always be > 0 in this case. For the others, just return the cached value if the - * widget is not set up. + * Volatile controls should always be part of static pipelines and the + * widget use_count would always be > 0 in this case. For the others, + * just return the cached value if the widget is not set up. */ if (!swidget->use_count) - return 0; + goto unlock;
/* * Select the IPC cmd and the ctrl_type based on the ctrl_cmd and the @@ -81,13 +88,20 @@ static int sof_ipc3_set_get_kcontrol_data(struct snd_sof_control *scontrol, bool sizeof(struct sof_abi_hdr); break; default: - return -EINVAL; + ret = -EINVAL; + goto unlock; }
cdata->rhdr.hdr.size = msg_bytes; cdata->elems_remaining = 0;
- return iops->set_get_data(sdev, cdata, cdata->rhdr.hdr.size, set); + ret = iops->set_get_data(sdev, cdata, cdata->rhdr.hdr.size, set); + +unlock: + if (lock) + mutex_unlock(&swidget->setup_mutex); + + return ret; }
static void snd_sof_refresh_control(struct snd_sof_control *scontrol) @@ -108,7 +122,7 @@ static void snd_sof_refresh_control(struct snd_sof_control *scontrol)
/* refresh the component data from DSP */ scontrol->comp_data_dirty = false; - ret = sof_ipc3_set_get_kcontrol_data(scontrol, false); + ret = sof_ipc3_set_get_kcontrol_data(scontrol, false, true); if (ret < 0) { dev_err(scomp->dev, "Failed to get control data: %d\n", ret);
@@ -156,7 +170,7 @@ static bool sof_ipc3_volume_put(struct snd_sof_control *scontrol,
/* notify DSP of mixer updates */ if (pm_runtime_active(scomp->dev)) { - int ret = sof_ipc3_set_get_kcontrol_data(scontrol, true); + int ret = sof_ipc3_set_get_kcontrol_data(scontrol, true, true);
if (ret < 0) { dev_err(scomp->dev, "Failed to set mixer updates for %s\n", @@ -204,7 +218,7 @@ static bool sof_ipc3_switch_put(struct snd_sof_control *scontrol,
/* notify DSP of mixer updates */ if (pm_runtime_active(scomp->dev)) { - int ret = sof_ipc3_set_get_kcontrol_data(scontrol, true); + int ret = sof_ipc3_set_get_kcontrol_data(scontrol, true, true);
if (ret < 0) { dev_err(scomp->dev, "Failed to set mixer updates for %s\n", @@ -252,7 +266,7 @@ static bool sof_ipc3_enum_put(struct snd_sof_control *scontrol,
/* notify DSP of enum updates */ if (pm_runtime_active(scomp->dev)) { - int ret = sof_ipc3_set_get_kcontrol_data(scontrol, true); + int ret = sof_ipc3_set_get_kcontrol_data(scontrol, true, true);
if (ret < 0) { dev_err(scomp->dev, "Failed to set enum updates for %s\n", @@ -324,7 +338,7 @@ static int sof_ipc3_bytes_put(struct snd_sof_control *scontrol,
/* notify DSP of byte control updates */ if (pm_runtime_active(scomp->dev)) - return sof_ipc3_set_get_kcontrol_data(scontrol, true); + return sof_ipc3_set_get_kcontrol_data(scontrol, true, true);
return 0; } @@ -438,7 +452,7 @@ static int sof_ipc3_bytes_ext_put(struct snd_sof_control *scontrol,
/* notify DSP of byte control updates */ if (pm_runtime_active(scomp->dev)) - return sof_ipc3_set_get_kcontrol_data(scontrol, true); + return sof_ipc3_set_get_kcontrol_data(scontrol, true, true);
return 0; } @@ -468,7 +482,7 @@ static int sof_ipc3_bytes_ext_volatile_get(struct snd_sof_control *scontrol, cdata->data->abi = SOF_ABI_VERSION;
/* get all the component data from DSP */ - ret = sof_ipc3_set_get_kcontrol_data(scontrol, false); + ret = sof_ipc3_set_get_kcontrol_data(scontrol, false, true); if (ret < 0) return ret;
@@ -647,7 +661,7 @@ static int sof_ipc3_widget_kcontrol_setup(struct snd_sof_dev *sdev, list_for_each_entry(scontrol, &sdev->kcontrol_list, list) if (scontrol->comp_id == swidget->comp_id) { /* set kcontrol data in DSP */ - ret = sof_ipc3_set_get_kcontrol_data(scontrol, true); + ret = sof_ipc3_set_get_kcontrol_data(scontrol, true, false); if (ret < 0) { dev_err(sdev->dev, "kcontrol %d set up failed for widget %s\n", @@ -664,7 +678,7 @@ static int sof_ipc3_widget_kcontrol_setup(struct snd_sof_dev *sdev, if (swidget->dynamic_pipeline_widget) continue;
- ret = sof_ipc3_set_get_kcontrol_data(scontrol, false); + ret = sof_ipc3_set_get_kcontrol_data(scontrol, false, false); if (ret < 0) dev_warn(sdev->dev, "kcontrol %d read failed for widget %s\n", diff --git a/sound/soc/sof/ipc3-topology.c b/sound/soc/sof/ipc3-topology.c index 88b9b9507231..dceb78bfe17c 100644 --- a/sound/soc/sof/ipc3-topology.c +++ b/sound/soc/sof/ipc3-topology.c @@ -2316,7 +2316,9 @@ static int sof_ipc3_tear_down_all_pipelines(struct snd_sof_dev *sdev, bool verif /* Do not free widgets for static pipelines with FW older than SOF2.2 */ if (!verify && !swidget->dynamic_pipeline_widget && SOF_FW_VER(v->major, v->minor, v->micro) < SOF_FW_VER(2, 2, 0)) { + mutex_lock(&swidget->setup_mutex); swidget->use_count = 0; + mutex_unlock(&swidget->setup_mutex); if (swidget->spipe) swidget->spipe->complete = 0; continue; diff --git a/sound/soc/sof/ipc4-control.c b/sound/soc/sof/ipc4-control.c index 0d5a578c3496..67bd2233fd9a 100644 --- a/sound/soc/sof/ipc4-control.c +++ b/sound/soc/sof/ipc4-control.c @@ -12,7 +12,8 @@ #include "ipc4-priv.h" #include "ipc4-topology.h"
-static int sof_ipc4_set_get_kcontrol_data(struct snd_sof_control *scontrol, bool set) +static int sof_ipc4_set_get_kcontrol_data(struct snd_sof_control *scontrol, + bool set, bool lock) { struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data; struct snd_soc_component *scomp = scontrol->scomp; @@ -21,6 +22,7 @@ static int sof_ipc4_set_get_kcontrol_data(struct snd_sof_control *scontrol, bool struct sof_ipc4_msg *msg = &cdata->msg; struct snd_sof_widget *swidget; bool widget_found = false; + int ret = 0;
/* find widget associated with the control */ list_for_each_entry(swidget, &sdev->widget_list, list) { @@ -35,23 +37,34 @@ static int sof_ipc4_set_get_kcontrol_data(struct snd_sof_control *scontrol, bool return -ENOENT; }
+ if (lock) + mutex_lock(&swidget->setup_mutex); + else + lockdep_assert_held(&swidget->setup_mutex); + /* - * Volatile controls should always be part of static pipelines and the widget use_count - * would always be > 0 in this case. For the others, just return the cached value if the - * widget is not set up. + * Volatile controls should always be part of static pipelines and the + * widget use_count would always be > 0 in this case. For the others, + * just return the cached value if the widget is not set up. */ if (!swidget->use_count) - return 0; + goto unlock;
msg->primary &= ~SOF_IPC4_MOD_INSTANCE_MASK; msg->primary |= SOF_IPC4_MOD_INSTANCE(swidget->instance_id);
- return iops->set_get_data(sdev, msg, msg->data_size, set); + ret = iops->set_get_data(sdev, msg, msg->data_size, set); + +unlock: + if (lock) + mutex_unlock(&swidget->setup_mutex); + + return ret; }
static int sof_ipc4_set_volume_data(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget, - struct snd_sof_control *scontrol) + struct snd_sof_control *scontrol, bool lock) { struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data; struct sof_ipc4_gain *gain = swidget->private; @@ -90,7 +103,7 @@ sof_ipc4_set_volume_data(struct snd_sof_dev *sdev, struct snd_sof_widget *swidge msg->data_ptr = &data; msg->data_size = sizeof(data);
- ret = sof_ipc4_set_get_kcontrol_data(scontrol, true); + ret = sof_ipc4_set_get_kcontrol_data(scontrol, true, lock); msg->data_ptr = NULL; msg->data_size = 0; if (ret < 0) { @@ -145,7 +158,7 @@ static bool sof_ipc4_volume_put(struct snd_sof_control *scontrol, return false; }
- ret = sof_ipc4_set_volume_data(sdev, swidget, scontrol); + ret = sof_ipc4_set_volume_data(sdev, swidget, scontrol, true); if (ret < 0) return false;
@@ -175,7 +188,7 @@ static int sof_ipc4_widget_kcontrol_setup(struct snd_sof_dev *sdev, struct snd_s
list_for_each_entry(scontrol, &sdev->kcontrol_list, list) if (scontrol->comp_id == swidget->comp_id) { - ret = sof_ipc4_set_volume_data(sdev, swidget, scontrol); + ret = sof_ipc4_set_volume_data(sdev, swidget, scontrol, false); if (ret < 0) { dev_err(sdev->dev, "%s: kcontrol %d set up failed for widget %s\n", __func__, scontrol->comp_id, swidget->widget->name); diff --git a/sound/soc/sof/sof-audio.c b/sound/soc/sof/sof-audio.c index 14b4b949d081..760621bfc802 100644 --- a/sound/soc/sof/sof-audio.c +++ b/sound/soc/sof/sof-audio.c @@ -28,7 +28,8 @@ static void sof_reset_route_setup_status(struct snd_sof_dev *sdev, struct snd_so } }
-int sof_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) +static int sof_widget_free_unlocked(struct snd_sof_dev *sdev, + struct snd_sof_widget *swidget) { const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg); struct snd_sof_widget *pipe_widget; @@ -70,7 +71,7 @@ int sof_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) * skip for static pipelines */ if (swidget->dynamic_pipeline_widget && swidget->id != snd_soc_dapm_scheduler) { - ret = sof_widget_free(sdev, pipe_widget); + ret = sof_widget_free_unlocked(sdev, pipe_widget); if (ret < 0 && !err) err = ret; } @@ -84,9 +85,21 @@ int sof_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
return err; } + +int sof_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) +{ + int ret; + + mutex_lock(&swidget->setup_mutex); + ret = sof_widget_free_unlocked(sdev, swidget); + mutex_unlock(&swidget->setup_mutex); + + return ret; +} EXPORT_SYMBOL(sof_widget_free);
-int sof_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) +static int sof_widget_setup_unlocked(struct snd_sof_dev *sdev, + struct snd_sof_widget *swidget) { const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg); bool use_count_decremented = false; @@ -116,7 +129,7 @@ int sof_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) goto use_count_dec; }
- ret = sof_widget_setup(sdev, swidget->spipe->pipe_widget); + ret = sof_widget_setup_unlocked(sdev, swidget->spipe->pipe_widget); if (ret < 0) goto use_count_dec; } @@ -160,19 +173,30 @@ int sof_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
widget_free: /* widget use_count and core ref_count will both be decremented by sof_widget_free() */ - sof_widget_free(sdev, swidget); + sof_widget_free_unlocked(sdev, swidget); use_count_decremented = true; core_put: snd_sof_dsp_core_put(sdev, swidget->core); pipe_widget_free: if (swidget->id != snd_soc_dapm_scheduler) - sof_widget_free(sdev, swidget->spipe->pipe_widget); + sof_widget_free_unlocked(sdev, swidget->spipe->pipe_widget); use_count_dec: if (!use_count_decremented) swidget->use_count--;
return ret; } + +int sof_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) +{ + int ret; + + mutex_lock(&swidget->setup_mutex); + ret = sof_widget_setup_unlocked(sdev, swidget); + mutex_unlock(&swidget->setup_mutex); + + return ret; +} EXPORT_SYMBOL(sof_widget_setup);
int sof_route_setup(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *wsource, diff --git a/sound/soc/sof/sof-audio.h b/sound/soc/sof/sof-audio.h index f1bbd1adc8b6..b0593b46d477 100644 --- a/sound/soc/sof/sof-audio.h +++ b/sound/soc/sof/sof-audio.h @@ -387,7 +387,16 @@ struct snd_sof_widget { * up in the DSP. */ bool prepared; - int use_count; /* use_count will be protected by the PCM mutex held by the core */ + + struct mutex setup_mutex; /* to protect the swidget setup and free operations */ + + /* + * use_count is protected by the PCM mutex held by the core and the + * setup_mutex against non stream domain races (kcontrol access for + * example) + */ + int use_count; + int core; int id; /* id is the DAPM widget type */ /* diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c index 33ca3067262b..e2f8cd9e278e 100644 --- a/sound/soc/sof/topology.c +++ b/sound/soc/sof/topology.c @@ -1405,6 +1405,8 @@ static int sof_widget_ready(struct snd_soc_component *scomp, int index, swidget->id = w->id; swidget->pipeline_id = index; swidget->private = NULL; + mutex_init(&swidget->setup_mutex); + ida_init(&swidget->src_queue_ida); ida_init(&swidget->sink_queue_ida);
If the pipeline setup fails at the first widget/pipeline then we will have no spipe stored under the pipeline_list->pipelines, the pipeline_list->count is 0.
If this is the case we would have a NULL pointer dereference if the execution is allowed to proceed.
Check for this condition along with the pipeline_list->pipelines check
Signed-off-by: Peter Ujfalusi peter.ujfalusi@linux.intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Reviewed-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com --- sound/soc/sof/ipc4-pcm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/sound/soc/sof/ipc4-pcm.c b/sound/soc/sof/ipc4-pcm.c index 2d89d3708ed0..521090d4498d 100644 --- a/sound/soc/sof/ipc4-pcm.c +++ b/sound/soc/sof/ipc4-pcm.c @@ -213,7 +213,7 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, pipeline_list = &spcm->stream[substream->stream].pipeline_list;
/* nothing to trigger if the list is empty */ - if (!pipeline_list->pipelines) + if (!pipeline_list->pipelines || !pipeline_list->count) return 0;
/* allocate memory for the pipeline data */
Hi,
wow, something messed up the subject line, which should have been:
[PATCH 00/18] ASoC: SOF: ipc4: Multi-stream playback and capture support
On Fri, 27 Jan 2023 14:00:13 +0200, Peter Ujfalusi wrote:
The following series will enable multi-stream support for playback and capture streams. Currently only a single PCM can be connected to a DAI, with the multi-stream support it is possible to connect multiple PCMs to a single DAI.
To achieve this we need to make sure that DAIs/AIF are only set up once since other stream could be connected to it later.
[...]
Applied to
broonie/sound.git for-next
Thanks!
[01/18] ASoC: SOF: ipc4-topology: No need to unbind routes within a pipeline commit: 9a62d87acee94919af1fe92f2412fff83dcbcda0 [02/18] ASoC: soc-pcm: Export widget_in_list() commit: 5edcf2a3aad41ee398ac011cda7bccca400b56f0 [03/18] ASoC: SOF: sof-audio: Set up/free DAI/AIF widgets only once commit: 73ea660947b52969214473434396a33d283c5ac8 [04/18] ASoC: SOF: sof-audio: Only process widgets in the connected widget list commit: 4639029b046bcab11bd566afa2979c68edeb338a [05/18] ASoC: SOF: pcm: do not free widgets during suspend trigger commit: 82b18242ae68214c44ccb13e993c2bc925f28428 [06/18] ASoC: SOF: topology: Set IPC-specific trigger order for DAI links commit: e380c9071032b89ea2e77b871792d908d0f15512 [07/18] ASoC: SOF: Introduce PCM setup/free PCM IPC ops commit: 7201a3d47e8a6a0b3a55125e70a9c650afabe7b0 [08/18] ASoC: SOF: ipc4-pcm: Define pcm_setup/free ops commit: ba223b3ad0b9f1753f0822c5c441a925cc82b63a [09/18] ASoC: SOF: ipc4: Add flag to skip triggering pipelines during FE DAI trigger commit: 37a26eec53b09b7054234b77200ce729601b0ccb [10/18] ASoC: SOF: sof-audio: Populate the PCM stream pipeline_info commit: 19137532dbe32ff2c8b5b1442c077bf3abff86f3 [11/18] ASoC: SOF: ipc4-pcm: Use the PCM stream's pipeline_info during trigger commit: 2d271af1af241e64726ada07c6bef6572f1be6a5 [12/18] ASoC: SOF: Introduce struct snd_sof_pipeline commit: 9c04363d222bc94d49d883458b2854334a848b5f [13/18] ASoC: SOF: ipc4-pcm: Rename 'data' variable to trigger_list commit: 6f9eb19a33d608ba36162a9ccbd34a77249fcc2e [14/18] ASoC: SOF: ipc4-pcm: Implement pipeline trigger reference counting commit: 32c4b69872e5fe5fd9517826be31dbf2c3dd917a [15/18] ASoC: SOF: ipc4-topology: Protect pipeline free with mutex commit: 6bc4d1b714aafc0ee3c7649c36aa19998b4c11f9 [16/18] ASoC: SOF: Avoid double decrementing use_count in sof_widget_setup on error commit: 955a6f131a50c3685c560ef7b75880d272337b33 [17/18] ASoC: SOF: Protect swidget->use_count with mutex for kcontrol access race commit: f94f3915274d22d5cd8b253e0533822b934f5f41 [18/18] ASoC: SOF: ipc4-pcm: Do not run the trigger pipelines if no spipe is stored commit: 251a2b11851531526260db1dbc5673a7d6177895
All being well this means that it will be integrated into the linux-next tree (usually sometime in the next 24 hours) and sent to Linus during the next merge window (or sooner if it is a bug fix), however if problems are discovered then the patch may be dropped or reverted.
You may get further e-mails resulting from automated or manual testing and review of the tree, please engage with people reporting problems and send followup patches addressing any issues that are reported if needed.
If any updates are required or you are submitting further changes they should be sent as incremental updates against current git, existing patches will not be replaced.
Please add any relevant lists and maintainers to the CCs when replying to this mail.
Thanks, Mark
participants (2)
-
Mark Brown
-
Peter Ujfalusi