Skip to content

Commit f28036f

Browse files
committed
fixup! ASoC: SOF: sof-audio: harden recursive widget free walk
Revert the sink-path traversal changes, keeping only the NULL spipe / pipe_widget checks in sof_widget_free_unlocked(). path->walking is DFS stack state: it means "this edge is currently being walked", and it has to be unwound by whoever set it. Not clearing it after the recursive call makes it a permanent 'visited' mark, so the FREE walk traverses a different set of edges than the SETUP walk, which unbalances swidget->use_count: - sof_walk_widgets_in_order() starts a walk from every aif_in (playback) or every dai_out/output (capture) widget in the list. With aggregated DAIs the branches reconverge on the shared widgets towards the FE. SETUP walks and increments them once per branch, but on FREE the first branch leaves the shared edges marked and the second branch skips them entirely, so use_count never reaches 0. The widget_free IPC is then never sent, the cores are never put and spipe->complete is never cleared: the DSP side leaks and the next stream reuses a widget that was never re-configured. - The same happens within a single walk whenever two branches of the list reconverge, as the shared edge below the merge point must be walked once per incoming branch. Clearing the flags after the loop cannot fix either case, and doing it for both directions of every widget in the list is unsafe on its own: walking is owned by the walk in progress, and the free walk does not hold the DAPM mutex, so a concurrent dapm_power_widgets() may have those same edges on its stack. The rest of the traversal churn is not needed either. FREE only sends IPC, it never removes DAPM paths, so the _safe iterator has nothing to protect against here - and against a concurrent topology teardown it would not help, as next_p is read before the recursion. p->sink cannot be NULL for a linked path, and the list can never be NULL as sof_widget_list_free() already checks it. Signed-off-by: Peter Ujfalusi <peter.ujfalusi@linux.intel.com> === everything below is dropped by 'git rebase --autosquash' === Once squashed, the only thing left of the original commit are the three hunks in sof_widget_free_unlocked(); the traversal is byte identical to the pre-2712ab5d30d6 state. The original subject no longer describes the patch, so use this message for the squashed commit: ASoC: SOF: sof-audio: do not dereference swidget->spipe unconditionally on free sof_widget_free_unlocked() dereferences swidget->spipe without checking it for two things: swidget->spipe->complete for a scheduler widget, and swidget->spipe->pipe_widget for the recursive free of the pipeline's scheduler widget. Both can be reached with spipe or pipe_widget not set, which oopses in the free path - where there is nothing left to bail out to. Check both before use and cache swidget->spipe in the local spipe variable that is already there. A widget with no pipeline has nothing to put or complete, and no scheduler widget to free, so skipping is the correct behaviour. No functional change for a widget that was successfully set up: sof_widget_setup_unlocked() already rejects a dynamic pipeline widget with no spipe or no spipe->pipe_widget with -EINVAL. Signed-off-by: Peter Ujfalusi <peter.ujfalusi@linux.intel.com> Two things to resolve before sending it out: - The oops was most likely hit via the compress free path (ipc4-compress.c sof_widget_list_free()), but the details were not recovered. Replace "can be reached with spipe or pipe_widget not set" with the actual path and add a Fixes: tag once known. - If it cannot be reproduced, drop the patch instead: as the last paragraph shows, the guards are unreachable for any widget that went through sof_widget_setup_unlocked().
1 parent 9f82f2a commit f28036f

1 file changed

Lines changed: 7 additions & 46 deletions

File tree

‎sound/soc/sof/sof-audio.c‎

Lines changed: 7 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -547,20 +547,16 @@ sof_prepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget
547547
* free all widgets in the sink path starting from the source widget
548548
* (DAI type for capture, AIF type for playback)
549549
*/
550-
static int sof_free_widgets_in_path_internal(struct snd_sof_dev *sdev,
551-
struct snd_soc_dapm_widget *widget,
552-
int dir, struct snd_sof_pcm *spcm,
553-
struct snd_soc_dapm_widget_list *list)
550+
static int sof_free_widgets_in_path(struct snd_sof_dev *sdev,
551+
struct snd_soc_dapm_widget *widget,
552+
int dir, struct snd_sof_pcm *spcm)
554553
{
554+
struct snd_soc_dapm_widget_list *list = spcm->stream[dir].list;
555555
struct snd_sof_widget *swidget = widget->dobj.private;
556556
struct snd_soc_dapm_path *p;
557-
struct snd_soc_dapm_path *next_p;
558557
int err;
559558
int ret = 0;
560559

561-
if (!list)
562-
return 0;
563-
564560
if (is_virtual_widget(sdev, widget, __func__))
565561
return 0;
566562

@@ -580,52 +576,23 @@ static int sof_free_widgets_in_path_internal(struct snd_sof_dev *sdev,
580576
ret = err;
581577
sink_free:
582578
/* free all widgets in the sink paths even in case of error to keep use counts balanced */
583-
snd_soc_dapm_widget_for_each_path_safe(widget, SND_SOC_DAPM_DIR_IN, p, next_p) {
579+
snd_soc_dapm_widget_for_each_sink_path(widget, p) {
584580
if (!p->walking) {
585-
if (!p->sink)
586-
continue;
587-
588581
if (!widget_in_list(list, p->sink))
589582
continue;
590583

591584
p->walking = true;
592585

593-
err = sof_free_widgets_in_path_internal(sdev, p->sink,
594-
dir, spcm, list);
586+
err = sof_free_widgets_in_path(sdev, p->sink, dir, spcm);
595587
if (err < 0)
596588
ret = err;
589+
p->walking = false;
597590
}
598591
}
599592

600593
return ret;
601594
}
602595

603-
static int sof_free_widgets_in_path(struct snd_sof_dev *sdev,
604-
struct snd_soc_dapm_widget *widget,
605-
int dir, struct snd_sof_pcm *spcm)
606-
{
607-
return sof_free_widgets_in_path_internal(sdev, widget, dir, spcm,
608-
spcm->stream[dir].list);
609-
}
610-
611-
static void sof_reset_path_walking_flags(struct snd_soc_dapm_widget_list *list)
612-
{
613-
struct snd_soc_dapm_widget *widget;
614-
struct snd_soc_dapm_path *p;
615-
int i;
616-
617-
if (!list)
618-
return;
619-
620-
for_each_dapm_widgets(list, i, widget) {
621-
snd_soc_dapm_widget_for_each_sink_path(widget, p)
622-
p->walking = false;
623-
624-
snd_soc_dapm_widget_for_each_source_path(widget, p)
625-
p->walking = false;
626-
}
627-
}
628-
629596
/*
630597
* set up all widgets in the sink path starting from the source widget
631598
* (DAI type for capture, AIF type for playback).
@@ -760,17 +727,11 @@ sof_walk_widgets_in_order(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm,
760727
return -EINVAL;
761728
}
762729
if (ret < 0) {
763-
if (op == SOF_WIDGET_FREE)
764-
sof_reset_path_walking_flags(list);
765-
766730
dev_err(sdev->dev, "Failed to %s connected widgets\n", str);
767731
return ret;
768732
}
769733
}
770734

771-
if (op == SOF_WIDGET_FREE)
772-
sof_reset_path_walking_flags(list);
773-
774735
return 0;
775736
}
776737

0 commit comments

Comments
 (0)