A linked prepare action can run the driver callback and pointer reset for one stream before the other members finish. A driver that closes ACK admission during prepare must not reopen it from that per-stream reset callback.
Add an optional prepare completion callback after all linked prepare actions and post actions finish. On an action failure, notify each member whose action ran, including the failing member, after moving opted-in members back to SETUP. Pre-action failures and standalone RESET do not call this hook. The callback runs under the PCM stream lock and must not sleep. Drivers without the callback keep the existing prepare failure behavior. Assisted-by: LLM Signed-off-by: Joonho Lee <[email protected]> --- include/sound/pcm.h | 10 ++++++++++ sound/core/pcm_native.c | 46 ++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/include/sound/pcm.h b/include/sound/pcm.h --- a/include/sound/pcm.h +++ b/include/sound/pcm.h @@ -77,6 +77,16 @@ unsigned long offset); int (*mmap)(struct snd_pcm_substream *substream, struct vm_area_struct *vma); int (*ack)(struct snd_pcm_substream *substream); + /* + * Optional prepare completion notification. Called with the PCM stream + * lock held after prepare and optional reset succeed for every linked + * member. On failure, called for members whose prepare action ran, + * including the failing member; the core first changes their state to + * SETUP. Members not reached and pre-action failures receive no callback. + * Standalone RESET does not call this callback. It must not sleep. + */ + void (*prepare_complete)(struct snd_pcm_substream *substream, + bool success); /* * Optional RESET operation, also used after a successful prepare. * Called with this substream's PCM lock held; atomic streams must not diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -1310,6 +1310,8 @@ snd_pcm_state_t state); void (*post_action)(struct snd_pcm_substream *substream, snd_pcm_state_t state); + void (*complete_action)(struct snd_pcm_substream *substream, + snd_pcm_state_t state, bool success); }; /* @@ -1350,12 +1352,23 @@ ops->undo_action(s1, state); } } + if (ops->complete_action) { + snd_pcm_group_for_each_entry(s1, substream) { + ops->complete_action(s1, state, false); + if (s1 == s) + break; + } + } s = NULL; /* unlock all */ goto _unlock; } } snd_pcm_group_for_each_entry(s, substream) { ops->post_action(s, state); + } + if (ops->complete_action) { + snd_pcm_group_for_each_entry(s, substream) + ops->complete_action(s, state, true); } _unlock: /* unlock streams */ @@ -1387,10 +1400,16 @@ if (res < 0) return res; res = ops->do_action(substream, state); - if (res == 0) + if (res == 0) { ops->post_action(substream, state); - else if (ops->undo_action) - ops->undo_action(substream, state); + if (ops->complete_action) + ops->complete_action(substream, state, true); + } else { + if (ops->undo_action) + ops->undo_action(substream, state); + if (ops->complete_action) + ops->complete_action(substream, state, false); + } return res; } @@ -2099,10 +2118,29 @@ snd_pcm_set_state(substream, SNDRV_PCM_STATE_PREPARED); } +static void snd_pcm_complete_prepare(struct snd_pcm_substream *substream, + snd_pcm_state_t state, bool success) +{ + if (!substream->ops->prepare_complete) + return; + + guard(pcm_stream_lock_irq)(substream); + if (!success && substream->runtime->state != SNDRV_PCM_STATE_DISCONNECTED) { + if (substream->runtime->state != SNDRV_PCM_STATE_SETUP) { + __snd_pcm_set_state(substream->runtime, SNDRV_PCM_STATE_SETUP); + snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MSTOP); + } + wake_up(&substream->runtime->sleep); + wake_up(&substream->runtime->tsleep); + } + substream->ops->prepare_complete(substream, success); +} + static const struct action_ops snd_pcm_action_prepare = { .pre_action = snd_pcm_pre_prepare, .do_action = snd_pcm_do_prepare, - .post_action = snd_pcm_post_prepare + .post_action = snd_pcm_post_prepare, + .complete_action = snd_pcm_complete_prepare }; /**
