diff --git a/include/sound/soc-dpcm.h b/include/sound/soc-dpcm.h index af24665e37e86b..f0598bff143a3f 100644 --- a/include/sound/soc-dpcm.h +++ b/include/sound/soc-dpcm.h @@ -77,6 +77,12 @@ struct snd_soc_dpcm { struct list_head list_be; struct list_head list_fe; + /* + * BE reference taken for the FE by START, RESUME or PAUSE_RELEASE, + * only accessed from the trigger path of the FE + */ + bool trigger_ref; + #ifdef CONFIG_DEBUG_FS struct dentry *debugfs_state; #endif diff --git a/sound/soc/soc-component.c b/sound/soc/soc-component.c index 34efb270ce2989..f8d3d078332cc9 100644 --- a/sound/soc/soc-component.c +++ b/sound/soc/soc-component.c @@ -12,6 +12,7 @@ #include #include #include +#include "soc-internal.h" #define soc_component_ret(dai, ret) _soc_component_ret(dai, __func__, ret) static inline int _soc_component_ret(struct snd_soc_component *component, const char *func, int ret) @@ -456,6 +457,7 @@ int snd_soc_component_compr_trigger(struct snd_compr_stream *cstream, int cmd) { struct snd_soc_pcm_runtime *rtd = cstream->private_data; struct snd_soc_component *component; + int unwind_cmd; int i, ret; for_each_rtd_components(rtd, i, component) { @@ -464,11 +466,30 @@ int snd_soc_component_compr_trigger(struct snd_compr_stream *cstream, int cmd) ret = component->driver->compress_ops->trigger( component, cstream, cmd); if (ret < 0) - return soc_component_ret(component, ret); + goto unwind; } } return 0; + +unwind: + ret = soc_component_ret(component, ret); + + unwind_cmd = snd_soc_trigger_unwind_cmd(cmd); + if (unwind_cmd < 0) + return ret; + + /* Unwind the components that have been triggered before the failing one */ + while (--i >= 0) { + component = rtd->components[i]; + if (component->driver->compress_ops && + component->driver->compress_ops->trigger) + component->driver->compress_ops->trigger(component, + cstream, + unwind_cmd); + } + + return ret; } EXPORT_SYMBOL_GPL(snd_soc_component_compr_trigger); diff --git a/sound/soc/soc-compress.c b/sound/soc/soc-compress.c index a941ed0908c667..6c5658724bc134 100644 --- a/sound/soc/soc-compress.c +++ b/sound/soc/soc-compress.c @@ -282,6 +282,7 @@ static int soc_compr_open_fe(struct snd_compr_stream *cstream) dpcm_path_put(&list); fe->dpcm[stream].state = SND_SOC_DPCM_STATE_OPEN; + fe->dpcm[stream].fe_pause = false; fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO; snd_soc_runtime_activate(fe, stream); @@ -365,6 +366,7 @@ static int soc_compr_trigger(struct snd_compr_stream *cstream, int cmd) struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); int stream = cstream->direction; /* SND_COMPRESS_xxx is same as SNDRV_PCM_STREAM_xxx */ + int unwind_cmd; int ret; snd_soc_dpcm_mutex_lock(rtd); @@ -374,8 +376,12 @@ static int soc_compr_trigger(struct snd_compr_stream *cstream, int cmd) goto out; ret = snd_soc_dai_compr_trigger(cpu_dai, cstream, cmd); - if (ret < 0) + if (ret < 0) { + unwind_cmd = snd_soc_trigger_unwind_cmd(cmd); + if (unwind_cmd >= 0) + snd_soc_component_compr_trigger(cstream, unwind_cmd); goto out; + } switch (cmd) { case SNDRV_PCM_TRIGGER_START: @@ -391,26 +397,45 @@ static int soc_compr_trigger(struct snd_compr_stream *cstream, int cmd) return ret; } +static int soc_compr_trigger_fe_only(struct snd_compr_stream *cstream, int cmd) +{ + struct snd_soc_pcm_runtime *fe = cstream->private_data; + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(fe, 0); + int unwind_cmd; + int ret; + + ret = snd_soc_dai_compr_trigger(cpu_dai, cstream, cmd); + if (ret < 0) + return ret; + + ret = snd_soc_component_compr_trigger(cstream, cmd); + if (ret < 0) { + unwind_cmd = snd_soc_trigger_unwind_cmd(cmd); + if (unwind_cmd >= 0) + snd_soc_dai_compr_trigger(cpu_dai, cstream, unwind_cmd); + } + + return ret; +} + static int soc_compr_trigger_fe_be(struct snd_compr_stream *cstream, int cmd, bool fe_first) { struct snd_soc_pcm_runtime *fe = cstream->private_data; - struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(fe, 0); + int unwind_cmd = snd_soc_trigger_unwind_cmd(cmd); int ret; if (fe_first) { dev_dbg(fe->dev, "ASoC: pre trigger FE %s cmd %d\n", fe->dai_link->name, cmd); - ret = snd_soc_dai_compr_trigger(cpu_dai, cstream, cmd); - if (ret < 0) - goto out; - - ret = snd_soc_component_compr_trigger(cstream, cmd); + ret = soc_compr_trigger_fe_only(cstream, cmd); if (ret < 0) goto out; ret = dpcm_be_dai_trigger(fe, cstream->direction, cmd); + if (ret < 0 && unwind_cmd >= 0) + soc_compr_trigger_fe_only(cstream, unwind_cmd); } else { dev_dbg(fe->dev, "ASoC: post trigger FE %s cmd %d\n", fe->dai_link->name, cmd); @@ -419,11 +444,9 @@ static int soc_compr_trigger_fe_be(struct snd_compr_stream *cstream, int cmd, if (ret < 0) goto out; - ret = snd_soc_dai_compr_trigger(cpu_dai, cstream, cmd); + ret = soc_compr_trigger_fe_only(cstream, cmd); if (ret < 0) - goto out; - - ret = snd_soc_component_compr_trigger(cstream, cmd); + dpcm_be_dai_trigger_unwind(fe, cstream->direction, cmd); } out: diff --git a/sound/soc/soc-internal.h b/sound/soc/soc-internal.h index 31559918355f87..f5631876268fb0 100644 --- a/sound/soc/soc-internal.h +++ b/sound/soc/soc-internal.h @@ -36,4 +36,28 @@ struct clk *snd_soc_dai_get_bclk(struct snd_soc_dai *dai); unsigned int snd_soc_dai_get_bclk_ratio(struct snd_soc_dai *dai); void snd_soc_dai_active_update(struct snd_soc_dai *dai, int stream, int action); +/* + * In soc-pcm + */ +void dpcm_be_dai_trigger_unwind(struct snd_soc_pcm_runtime *fe, int stream, + int cmd); + +/* + * Get the trigger command to unwind a failed START, RESUME or PAUSE_RELEASE. + * Returns -EINVAL for commands that are not unwound. + */ +static inline int snd_soc_trigger_unwind_cmd(int cmd) +{ + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + return SNDRV_PCM_TRIGGER_STOP; + case SNDRV_PCM_TRIGGER_RESUME: + return SNDRV_PCM_TRIGGER_SUSPEND; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + return SNDRV_PCM_TRIGGER_PAUSE_PUSH; + default: + return -EINVAL; + } +} + #endif /* __SOC_INTERNAL_H */ diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index ef8ccf52798eb6..531a85e0378525 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -1766,6 +1766,7 @@ int dpcm_be_dai_startup(struct snd_soc_pcm_runtime *fe, int stream) goto unwind; } be->dpcm[stream].be_start = 0; + be->dpcm[stream].be_pause = 0; be->dpcm[stream].state = SND_SOC_DPCM_STATE_OPEN; count++; } @@ -1997,6 +1998,7 @@ static int dpcm_fe_dai_startup(struct snd_pcm_substream *fe_substream) goto unwind; fe->dpcm[stream].state = SND_SOC_DPCM_STATE_OPEN; + fe->dpcm[stream].fe_pause = false; dpcm_runtime_setup_fe(fe_substream); @@ -2216,180 +2218,252 @@ static int dpcm_fe_dai_hw_params(struct snd_pcm_substream *substream, return soc_pcm_ret(fe, ret); } -int dpcm_be_dai_trigger(struct snd_soc_pcm_runtime *fe, int stream, - int cmd) +static int dpcm_be_dai_trigger_be(struct snd_soc_pcm_runtime *fe, + struct snd_soc_dpcm *dpcm, int stream, + int cmd, bool rollback) { - struct snd_soc_pcm_runtime *be; + struct snd_soc_pcm_runtime *be = dpcm->be; + struct snd_pcm_substream *be_substream; bool pause_stop_transition; - struct snd_soc_dpcm *dpcm; + bool suspend_to_pause; unsigned long flags; int ret = 0; - for_each_dpcm_be(fe, stream, dpcm) { - struct snd_pcm_substream *be_substream; + be_substream = snd_soc_dpcm_get_substream(be, stream); - be = dpcm->be; - be_substream = snd_soc_dpcm_get_substream(be, stream); + snd_pcm_stream_lock_irqsave_nested(be_substream, flags); - snd_pcm_stream_lock_irqsave_nested(be_substream, flags); + /* is this op for this BE ? */ + if (!snd_soc_dpcm_can_be_update(fe, be, stream)) + goto next; - /* is this op for this BE ? */ - if (!snd_soc_dpcm_can_be_update(fe, be, stream)) - goto next; - - dev_dbg(be->dev, "ASoC: trigger BE %s cmd %d\n", - be->dai_link->name, cmd); + dev_dbg(be->dev, "ASoC: trigger BE %s cmd %d\n", + be->dai_link->name, cmd); - switch (cmd) { - case SNDRV_PCM_TRIGGER_START: - if (!be->dpcm[stream].be_start && - (be->dpcm[stream].state != SND_SOC_DPCM_STATE_PREPARE) && - (be->dpcm[stream].state != SND_SOC_DPCM_STATE_STOP) && - (be->dpcm[stream].state != SND_SOC_DPCM_STATE_PAUSED)) - goto next; + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (!be->dpcm[stream].be_start && + be->dpcm[stream].state != SND_SOC_DPCM_STATE_PREPARE && + be->dpcm[stream].state != SND_SOC_DPCM_STATE_STOP && + be->dpcm[stream].state != SND_SOC_DPCM_STATE_PAUSED) + goto next; - be->dpcm[stream].be_start++; - if (be->dpcm[stream].be_start != 1) - goto next; + be->dpcm[stream].be_start++; + dpcm->trigger_ref = true; + if (be->dpcm[stream].be_start != 1) + goto next; - if (be->dpcm[stream].state == SND_SOC_DPCM_STATE_PAUSED) - ret = soc_pcm_trigger(be_substream, - SNDRV_PCM_TRIGGER_PAUSE_RELEASE); - else - ret = soc_pcm_trigger(be_substream, - SNDRV_PCM_TRIGGER_START); - if (ret) { - be->dpcm[stream].be_start--; - goto next; - } + if (be->dpcm[stream].state == SND_SOC_DPCM_STATE_PAUSED) + ret = soc_pcm_trigger(be_substream, + SNDRV_PCM_TRIGGER_PAUSE_RELEASE); + else + ret = soc_pcm_trigger(be_substream, + SNDRV_PCM_TRIGGER_START); + if (ret) { + be->dpcm[stream].be_start--; + dpcm->trigger_ref = false; + goto next; + } - be->dpcm[stream].state = SND_SOC_DPCM_STATE_START; - break; - case SNDRV_PCM_TRIGGER_RESUME: - if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_SUSPEND)) - goto next; + be->dpcm[stream].state = SND_SOC_DPCM_STATE_START; + break; + case SNDRV_PCM_TRIGGER_RESUME: + if (!be->dpcm[stream].be_start && + be->dpcm[stream].state != SND_SOC_DPCM_STATE_SUSPEND && + be->dpcm[stream].state != SND_SOC_DPCM_STATE_PAUSED) + goto next; - be->dpcm[stream].be_start++; - if (be->dpcm[stream].be_start != 1) - goto next; + be->dpcm[stream].be_start++; + dpcm->trigger_ref = true; + if (be->dpcm[stream].be_start != 1) + goto next; + if (be->dpcm[stream].state == SND_SOC_DPCM_STATE_PAUSED) + ret = soc_pcm_trigger(be_substream, + SNDRV_PCM_TRIGGER_PAUSE_RELEASE); + else ret = soc_pcm_trigger(be_substream, cmd); - if (ret) { - be->dpcm[stream].be_start--; - goto next; - } + if (ret) { + be->dpcm[stream].be_start--; + dpcm->trigger_ref = false; + goto next; + } - be->dpcm[stream].state = SND_SOC_DPCM_STATE_START; - break; - case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - if (!be->dpcm[stream].be_start && - (be->dpcm[stream].state != SND_SOC_DPCM_STATE_START) && - (be->dpcm[stream].state != SND_SOC_DPCM_STATE_PAUSED)) - goto next; + be->dpcm[stream].state = SND_SOC_DPCM_STATE_START; + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (!be->dpcm[stream].be_start && + be->dpcm[stream].state != SND_SOC_DPCM_STATE_START && + be->dpcm[stream].state != SND_SOC_DPCM_STATE_PAUSED) + goto next; - fe->dpcm[stream].fe_pause = false; - be->dpcm[stream].be_pause--; + fe->dpcm[stream].fe_pause = false; + be->dpcm[stream].be_pause--; - be->dpcm[stream].be_start++; - if (be->dpcm[stream].be_start != 1) - goto next; + be->dpcm[stream].be_start++; + dpcm->trigger_ref = true; + if (be->dpcm[stream].be_start != 1) + goto next; - ret = soc_pcm_trigger(be_substream, cmd); - if (ret) { - be->dpcm[stream].be_start--; - goto next; - } + ret = soc_pcm_trigger(be_substream, cmd); + if (ret) { + be->dpcm[stream].be_start--; + dpcm->trigger_ref = false; + fe->dpcm[stream].fe_pause = true; + be->dpcm[stream].be_pause++; + goto next; + } - be->dpcm[stream].state = SND_SOC_DPCM_STATE_START; - break; - case SNDRV_PCM_TRIGGER_STOP: - if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_START) && - (be->dpcm[stream].state != SND_SOC_DPCM_STATE_PAUSED)) - goto next; + be->dpcm[stream].state = SND_SOC_DPCM_STATE_START; + break; + case SNDRV_PCM_TRIGGER_STOP: + if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_START && + be->dpcm[stream].state != SND_SOC_DPCM_STATE_PAUSED) + goto next; - if (be->dpcm[stream].state == SND_SOC_DPCM_STATE_START) - be->dpcm[stream].be_start--; + if (be->dpcm[stream].state == SND_SOC_DPCM_STATE_START) + be->dpcm[stream].be_start--; - if (be->dpcm[stream].be_start != 0) - goto next; + if (be->dpcm[stream].be_start != 0) + goto next; - pause_stop_transition = false; - if (fe->dpcm[stream].fe_pause) { - pause_stop_transition = true; - fe->dpcm[stream].fe_pause = false; - be->dpcm[stream].be_pause--; - } + /* + * A STOP to roll back a failed START is not a pause/stop + * transition, the BE pause reference has not been taken by START + */ + pause_stop_transition = false; + if (!rollback && fe->dpcm[stream].fe_pause) { + pause_stop_transition = true; + fe->dpcm[stream].fe_pause = false; + be->dpcm[stream].be_pause--; + } - if (be->dpcm[stream].be_pause != 0) - ret = soc_pcm_trigger(be_substream, SNDRV_PCM_TRIGGER_PAUSE_PUSH); - else - ret = soc_pcm_trigger(be_substream, SNDRV_PCM_TRIGGER_STOP); - - if (ret) { - if (be->dpcm[stream].state == SND_SOC_DPCM_STATE_START) - be->dpcm[stream].be_start++; - if (pause_stop_transition) { - fe->dpcm[stream].fe_pause = true; - be->dpcm[stream].be_pause++; - } - goto next; + if (be->dpcm[stream].be_pause != 0) + ret = soc_pcm_trigger(be_substream, SNDRV_PCM_TRIGGER_PAUSE_PUSH); + else + ret = soc_pcm_trigger(be_substream, SNDRV_PCM_TRIGGER_STOP); + + if (ret) { + if (be->dpcm[stream].state == SND_SOC_DPCM_STATE_START) + be->dpcm[stream].be_start++; + if (pause_stop_transition) { + fe->dpcm[stream].fe_pause = true; + be->dpcm[stream].be_pause++; } + goto next; + } - if (be->dpcm[stream].be_pause != 0) - be->dpcm[stream].state = SND_SOC_DPCM_STATE_PAUSED; - else - be->dpcm[stream].state = SND_SOC_DPCM_STATE_STOP; + if (be->dpcm[stream].be_pause != 0) + be->dpcm[stream].state = SND_SOC_DPCM_STATE_PAUSED; + else + be->dpcm[stream].state = SND_SOC_DPCM_STATE_STOP; - break; - case SNDRV_PCM_TRIGGER_SUSPEND: - if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_START) - goto next; + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_START) + goto next; - be->dpcm[stream].be_start--; - if (be->dpcm[stream].be_start != 0) - goto next; + be->dpcm[stream].be_start--; + if (be->dpcm[stream].be_start != 0) + goto next; + /* + * A SUSPEND to roll back a failed RESUME must not suspend the BE + * under a paused FE, pause the BE as the PAUSE_PUSH of that FE + * would have done without the RESUME. + */ + suspend_to_pause = rollback && be->dpcm[stream].be_pause != 0; + if (suspend_to_pause) + ret = soc_pcm_trigger(be_substream, + SNDRV_PCM_TRIGGER_PAUSE_PUSH); + else ret = soc_pcm_trigger(be_substream, cmd); - if (ret) { - be->dpcm[stream].be_start++; - goto next; - } + if (ret) { + be->dpcm[stream].be_start++; + goto next; + } + if (suspend_to_pause) + be->dpcm[stream].state = SND_SOC_DPCM_STATE_PAUSED; + else be->dpcm[stream].state = SND_SOC_DPCM_STATE_SUSPEND; - break; - case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_START) - goto next; - - fe->dpcm[stream].fe_pause = true; - be->dpcm[stream].be_pause++; + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_START) + goto next; - be->dpcm[stream].be_start--; - if (be->dpcm[stream].be_start != 0) - goto next; + fe->dpcm[stream].fe_pause = true; + be->dpcm[stream].be_pause++; - ret = soc_pcm_trigger(be_substream, cmd); - if (ret) { - be->dpcm[stream].be_start++; - goto next; - } + be->dpcm[stream].be_start--; + if (be->dpcm[stream].be_start != 0) + goto next; - be->dpcm[stream].state = SND_SOC_DPCM_STATE_PAUSED; - break; + ret = soc_pcm_trigger(be_substream, cmd); + if (ret) { + be->dpcm[stream].be_start++; + goto next; } + + be->dpcm[stream].state = SND_SOC_DPCM_STATE_PAUSED; + break; + } next: - snd_pcm_stream_unlock_irqrestore(be_substream, flags); + snd_pcm_stream_unlock_irqrestore(be_substream, flags); + + return ret; +} + +int dpcm_be_dai_trigger(struct snd_soc_pcm_runtime *fe, int stream, int cmd) +{ + struct snd_soc_dpcm *dpcm; + int ret = 0; + + for_each_dpcm_be(fe, stream, dpcm) + dpcm->trigger_ref = false; + + for_each_dpcm_be(fe, stream, dpcm) { + ret = dpcm_be_dai_trigger_be(fe, dpcm, stream, cmd, false); if (ret) break; } + + /* Unwind the BEs that have been triggered before the failing one */ + if (ret) + dpcm_be_dai_trigger_unwind(fe, stream, cmd); + return soc_pcm_ret(fe, ret); } EXPORT_SYMBOL_GPL(dpcm_be_dai_trigger); +/* + * Unwind a successful or partially failed START, RESUME or PAUSE_RELEASE on + * the BEs. Only the BEs which took a reference for the FE in the last + * dpcm_be_dai_trigger() call are unwound. + */ +void dpcm_be_dai_trigger_unwind(struct snd_soc_pcm_runtime *fe, int stream, + int cmd) +{ + int unwind_cmd = snd_soc_trigger_unwind_cmd(cmd); + struct snd_soc_dpcm *dpcm; + + if (unwind_cmd < 0) + return; + + list_for_each_entry_reverse(dpcm, &fe->dpcm[stream].be_clients, list_be) { + if (!dpcm->trigger_ref) + continue; + + dpcm->trigger_ref = false; + dpcm_be_dai_trigger_be(fe, dpcm, stream, unwind_cmd, true); + } +} + static int dpcm_dai_trigger_fe_be(struct snd_pcm_substream *substream, int cmd, bool fe_first) { struct snd_soc_pcm_runtime *fe = snd_soc_substream_to_rtd(substream); + int unwind_cmd = snd_soc_trigger_unwind_cmd(cmd); int ret; /* call trigger on the frontend before the backend. */ @@ -2402,6 +2476,8 @@ static int dpcm_dai_trigger_fe_be(struct snd_pcm_substream *substream, goto end; ret = dpcm_be_dai_trigger(fe, substream->stream, cmd); + if (ret < 0 && unwind_cmd >= 0) + soc_pcm_trigger(substream, unwind_cmd); } /* call trigger on the frontend after the backend. */ else { @@ -2413,6 +2489,8 @@ static int dpcm_dai_trigger_fe_be(struct snd_pcm_substream *substream, fe->dai_link->name, cmd); ret = soc_pcm_trigger(substream, cmd); + if (ret < 0) + dpcm_be_dai_trigger_unwind(fe, substream->stream, cmd); } end: return snd_soc_ret(fe->dev, ret, "trigger FE cmd: %d failed\n", cmd); @@ -2442,6 +2520,25 @@ static int dpcm_fe_dai_do_trigger(struct snd_pcm_substream *substream, int cmd) goto out; } + switch (cmd) { + case SNDRV_PCM_TRIGGER_STOP: + /* + * A failed START is unwound already, ignore the undo STOP from + * the PCM core if the FE has not been started. + */ + if (fe->dpcm[stream].state != SND_SOC_DPCM_STATE_START && + fe->dpcm[stream].state != SND_SOC_DPCM_STATE_PAUSED) + goto out; + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + /* Same for the undo PAUSE_PUSH of a failed PAUSE_RELEASE */ + if (fe->dpcm[stream].state != SND_SOC_DPCM_STATE_START) + goto out; + break; + default: + break; + } + switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: