Skip to content

Commit ea9d0d7

Browse files
tiwaibroonie
authored andcommitted
ASoC: dpcm: Fix race between FE/BE updates and trigger
DPCM can update the FE/BE connection states totally asynchronously from the FE's PCM state. Most of FE/BE state changes are protected by mutex, so that they won't race, but there are still some actions that are uncovered. For example, suppose to switch a BE while a FE's stream is running. This would call soc_dpcm_runtime_update(), which sets FE's runtime_update flag, then sets up and starts BEs, and clears FE's runtime_update flag again. When a device emits XRUN during this operation, the PCM core triggers snd_pcm_stop(XRUN). Since the trigger action is an atomic ops, this isn't blocked by the mutex, thus it kicks off DPCM's trigger action. It eventually updates and clears FE's runtime_update flag while soc_dpcm_runtime_update() is running concurrently, and it results in confusion. Usually, for avoiding such a race, we take a lock. There is a PCM stream lock for that purpose. However, as already mentioned, the trigger action is atomic, and we can't take the lock for the whole soc_dpcm_runtime_update() or other operations that include the lengthy jobs like hw_params or prepare. This patch provides an alternative solution. This adds a way to defer the conflicting trigger callback to be executed at the end of FE/BE state changes. For doing it, two things are introduced: - Each runtime_update state change of FEs is protected via PCM stream lock. - The FE's trigger callback checks the runtime_update flag. If it's not set, the trigger action is executed there. If set, mark the pending trigger action and returns immediately. - At the exit of runtime_update state change, it checks whether the pending trigger is present. If yes, it executes the trigger action at this point. Reported-and-tested-by: Qiao Zhou <[email protected]> Signed-off-by: Takashi Iwai <[email protected]> Acked-by: Liam Girdwood <[email protected]> Signed-off-by: Mark Brown <[email protected]> Cc: [email protected]
1 parent f114040 commit ea9d0d7

File tree

2 files changed

+58
-16
lines changed

2 files changed

+58
-16
lines changed

include/sound/soc-dpcm.h

+2
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ struct snd_soc_dpcm_runtime {
102102
/* state and update */
103103
enum snd_soc_dpcm_update runtime_update;
104104
enum snd_soc_dpcm_state state;
105+
106+
int trigger_pending; /* trigger cmd + 1 if pending, 0 if not */
105107
};
106108

107109
/* can this BE stop and free */

sound/soc/soc-pcm.c

+56-16
Original file line numberDiff line numberDiff line change
@@ -1522,13 +1522,36 @@ static void dpcm_set_fe_runtime(struct snd_pcm_substream *substream)
15221522
dpcm_init_runtime_hw(runtime, &cpu_dai_drv->capture);
15231523
}
15241524

1525+
static int dpcm_fe_dai_do_trigger(struct snd_pcm_substream *substream, int cmd);
1526+
1527+
/* Set FE's runtime_update state; the state is protected via PCM stream lock
1528+
* for avoiding the race with trigger callback.
1529+
* If the state is unset and a trigger is pending while the previous operation,
1530+
* process the pending trigger action here.
1531+
*/
1532+
static void dpcm_set_fe_update_state(struct snd_soc_pcm_runtime *fe,
1533+
int stream, enum snd_soc_dpcm_update state)
1534+
{
1535+
struct snd_pcm_substream *substream =
1536+
snd_soc_dpcm_get_substream(fe, stream);
1537+
1538+
snd_pcm_stream_lock_irq(substream);
1539+
if (state == SND_SOC_DPCM_UPDATE_NO && fe->dpcm[stream].trigger_pending) {
1540+
dpcm_fe_dai_do_trigger(substream,
1541+
fe->dpcm[stream].trigger_pending - 1);
1542+
fe->dpcm[stream].trigger_pending = 0;
1543+
}
1544+
fe->dpcm[stream].runtime_update = state;
1545+
snd_pcm_stream_unlock_irq(substream);
1546+
}
1547+
15251548
static int dpcm_fe_dai_startup(struct snd_pcm_substream *fe_substream)
15261549
{
15271550
struct snd_soc_pcm_runtime *fe = fe_substream->private_data;
15281551
struct snd_pcm_runtime *runtime = fe_substream->runtime;
15291552
int stream = fe_substream->stream, ret = 0;
15301553

1531-
fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE;
1554+
dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE);
15321555

15331556
ret = dpcm_be_dai_startup(fe, fe_substream->stream);
15341557
if (ret < 0) {
@@ -1550,13 +1573,13 @@ static int dpcm_fe_dai_startup(struct snd_pcm_substream *fe_substream)
15501573
dpcm_set_fe_runtime(fe_substream);
15511574
snd_pcm_limit_hw_rates(runtime);
15521575

1553-
fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO;
1576+
dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO);
15541577
return 0;
15551578

15561579
unwind:
15571580
dpcm_be_dai_startup_unwind(fe, fe_substream->stream);
15581581
be_err:
1559-
fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO;
1582+
dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO);
15601583
return ret;
15611584
}
15621585

@@ -1603,7 +1626,7 @@ static int dpcm_fe_dai_shutdown(struct snd_pcm_substream *substream)
16031626
struct snd_soc_pcm_runtime *fe = substream->private_data;
16041627
int stream = substream->stream;
16051628

1606-
fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE;
1629+
dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE);
16071630

16081631
/* shutdown the BEs */
16091632
dpcm_be_dai_shutdown(fe, substream->stream);
@@ -1617,7 +1640,7 @@ static int dpcm_fe_dai_shutdown(struct snd_pcm_substream *substream)
16171640
dpcm_dapm_stream_event(fe, stream, SND_SOC_DAPM_STREAM_STOP);
16181641

16191642
fe->dpcm[stream].state = SND_SOC_DPCM_STATE_CLOSE;
1620-
fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO;
1643+
dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO);
16211644
return 0;
16221645
}
16231646

@@ -1665,7 +1688,7 @@ static int dpcm_fe_dai_hw_free(struct snd_pcm_substream *substream)
16651688
int err, stream = substream->stream;
16661689

16671690
mutex_lock_nested(&fe->card->mutex, SND_SOC_CARD_CLASS_RUNTIME);
1668-
fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE;
1691+
dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE);
16691692

16701693
dev_dbg(fe->dev, "ASoC: hw_free FE %s\n", fe->dai_link->name);
16711694

@@ -1680,7 +1703,7 @@ static int dpcm_fe_dai_hw_free(struct snd_pcm_substream *substream)
16801703
err = dpcm_be_dai_hw_free(fe, stream);
16811704

16821705
fe->dpcm[stream].state = SND_SOC_DPCM_STATE_HW_FREE;
1683-
fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO;
1706+
dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO);
16841707

16851708
mutex_unlock(&fe->card->mutex);
16861709
return 0;
@@ -1773,7 +1796,7 @@ static int dpcm_fe_dai_hw_params(struct snd_pcm_substream *substream,
17731796
int ret, stream = substream->stream;
17741797

17751798
mutex_lock_nested(&fe->card->mutex, SND_SOC_CARD_CLASS_RUNTIME);
1776-
fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE;
1799+
dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE);
17771800

17781801
memcpy(&fe->dpcm[substream->stream].hw_params, params,
17791802
sizeof(struct snd_pcm_hw_params));
@@ -1796,7 +1819,7 @@ static int dpcm_fe_dai_hw_params(struct snd_pcm_substream *substream,
17961819
fe->dpcm[stream].state = SND_SOC_DPCM_STATE_HW_PARAMS;
17971820

17981821
out:
1799-
fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO;
1822+
dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO);
18001823
mutex_unlock(&fe->card->mutex);
18011824
return ret;
18021825
}
@@ -1910,7 +1933,7 @@ int dpcm_be_dai_trigger(struct snd_soc_pcm_runtime *fe, int stream,
19101933
}
19111934
EXPORT_SYMBOL_GPL(dpcm_be_dai_trigger);
19121935

1913-
static int dpcm_fe_dai_trigger(struct snd_pcm_substream *substream, int cmd)
1936+
static int dpcm_fe_dai_do_trigger(struct snd_pcm_substream *substream, int cmd)
19141937
{
19151938
struct snd_soc_pcm_runtime *fe = substream->private_data;
19161939
int stream = substream->stream, ret;
@@ -1984,6 +2007,23 @@ static int dpcm_fe_dai_trigger(struct snd_pcm_substream *substream, int cmd)
19842007
return ret;
19852008
}
19862009

2010+
static int dpcm_fe_dai_trigger(struct snd_pcm_substream *substream, int cmd)
2011+
{
2012+
struct snd_soc_pcm_runtime *fe = substream->private_data;
2013+
int stream = substream->stream;
2014+
2015+
/* if FE's runtime_update is already set, we're in race;
2016+
* process this trigger later at exit
2017+
*/
2018+
if (fe->dpcm[stream].runtime_update != SND_SOC_DPCM_UPDATE_NO) {
2019+
fe->dpcm[stream].trigger_pending = cmd + 1;
2020+
return 0; /* delayed, assuming it's successful */
2021+
}
2022+
2023+
/* we're alone, let's trigger */
2024+
return dpcm_fe_dai_do_trigger(substream, cmd);
2025+
}
2026+
19872027
int dpcm_be_dai_prepare(struct snd_soc_pcm_runtime *fe, int stream)
19882028
{
19892029
struct snd_soc_dpcm *dpcm;
@@ -2027,7 +2067,7 @@ static int dpcm_fe_dai_prepare(struct snd_pcm_substream *substream)
20272067

20282068
dev_dbg(fe->dev, "ASoC: prepare FE %s\n", fe->dai_link->name);
20292069

2030-
fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE;
2070+
dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE);
20312071

20322072
/* there is no point preparing this FE if there are no BEs */
20332073
if (list_empty(&fe->dpcm[stream].be_clients)) {
@@ -2054,7 +2094,7 @@ static int dpcm_fe_dai_prepare(struct snd_pcm_substream *substream)
20542094
fe->dpcm[stream].state = SND_SOC_DPCM_STATE_PREPARE;
20552095

20562096
out:
2057-
fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO;
2097+
dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO);
20582098
mutex_unlock(&fe->card->mutex);
20592099

20602100
return ret;
@@ -2201,11 +2241,11 @@ static int dpcm_run_new_update(struct snd_soc_pcm_runtime *fe, int stream)
22012241
{
22022242
int ret;
22032243

2204-
fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_BE;
2244+
dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_BE);
22052245
ret = dpcm_run_update_startup(fe, stream);
22062246
if (ret < 0)
22072247
dev_err(fe->dev, "ASoC: failed to startup some BEs\n");
2208-
fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO;
2248+
dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO);
22092249

22102250
return ret;
22112251
}
@@ -2214,11 +2254,11 @@ static int dpcm_run_old_update(struct snd_soc_pcm_runtime *fe, int stream)
22142254
{
22152255
int ret;
22162256

2217-
fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_BE;
2257+
dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_BE);
22182258
ret = dpcm_run_update_shutdown(fe, stream);
22192259
if (ret < 0)
22202260
dev_err(fe->dev, "ASoC: failed to shutdown some BEs\n");
2221-
fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO;
2261+
dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO);
22222262

22232263
return ret;
22242264
}

0 commit comments

Comments
 (0)