diff mbox

[Question,about,DPCM] dpcm_run_new_update races again xrun

Message ID s5h1tpkb4y5.wl-tiwai@suse.de (mailing list archive)
State Superseded
Delegated to: Takashi Iwai
Headers show

Commit Message

Takashi Iwai Nov. 3, 2014, 4:54 p.m. UTC
At Mon, 03 Nov 2014 15:42:16 +0100,
Takashi Iwai wrote:
> 
> At Mon, 03 Nov 2014 13:32:04 +0000,
> Liam Girdwood wrote:
> > 
> > On Mon, 2014-11-03 at 03:28 -0800, Qiao Zhou wrote:
> > > Hi Mark, Liam
> > > 
> > > I met one BE not-function issue when developing DPCM feature, and found
> > > dpcm_run_new_update is not protected well against xrun(interrupt context).
> > > Could you help to give some suggestions?
> > > 
> > 
> > I'm wondering if this would be better solved by improving the locking so
> > that an XRUN could not run at the same time as the runtime update. Both
> > functions are async, but are only protected by a mutex atm (like the
> > rest of PCM ops except trigger which is atomic). We maybe need to add a
> > spinlock to both runtime_update() and dpcm_fe_dai_trigger() 
> 
> Be careful, you cannot take spinlock while hw_params, for example.
> 
> Why do you need to set runtime_update to NO in the trigger callback in
> the first place?  The trigger may influence on the FE streaming state,
> but not on the FE/BE connection state, right?

Thinking of this again, this doesn't look like a good suggestion,
either.

As mentioned, we can't take a lock for the whole lengthy operation
like prepare or hw_params.  So, instead of taking a lock, the trigger
should be delayed when some operation is being performed.  (I thought
at first just performing FE's trigger and delaying BE later, but FE/BE
trigger can be more complex than that, so it ends up with the whole
delayed trigger.)

The patch below is a quick hack (only compile tested!).  It uses PCM's
stream lock for protecting against the race for the state transition,
and the trigger callback checks the state, aborts immediately if there
is a concurrent operation.  At the exit of state change, it invokes
the delayed trigger.

Let me know if this really works as imagined.  Then I'll cook up a
proper patch.


Takashi

---

Comments

Liam Girdwood Nov. 3, 2014, 5:20 p.m. UTC | #1
On Mon, 2014-11-03 at 17:54 +0100, Takashi Iwai wrote:
> At Mon, 03 Nov 2014 15:42:16 +0100,
> Takashi Iwai wrote:
> > 
> > At Mon, 03 Nov 2014 13:32:04 +0000,
> > Liam Girdwood wrote:
> > > 
> > > On Mon, 2014-11-03 at 03:28 -0800, Qiao Zhou wrote:
> > > > Hi Mark, Liam
> > > > 
> > > > I met one BE not-function issue when developing DPCM feature, and found
> > > > dpcm_run_new_update is not protected well against xrun(interrupt context).
> > > > Could you help to give some suggestions?
> > > > 
> > > 
> > > I'm wondering if this would be better solved by improving the locking so
> > > that an XRUN could not run at the same time as the runtime update. Both
> > > functions are async, but are only protected by a mutex atm (like the
> > > rest of PCM ops except trigger which is atomic). We maybe need to add a
> > > spinlock to both runtime_update() and dpcm_fe_dai_trigger() 
> > 
> > Be careful, you cannot take spinlock while hw_params, for example.
> > 
> > Why do you need to set runtime_update to NO in the trigger callback in
> > the first place?  The trigger may influence on the FE streaming state,
> > but not on the FE/BE connection state, right?
> 
> Thinking of this again, this doesn't look like a good suggestion,
> either.
> 
> As mentioned, we can't take a lock for the whole lengthy operation
> like prepare or hw_params.  So, instead of taking a lock, the trigger
> should be delayed when some operation is being performed.  (I thought
> at first just performing FE's trigger and delaying BE later, but FE/BE
> trigger can be more complex than that, so it ends up with the whole
> delayed trigger.)
> 
> The patch below is a quick hack (only compile tested!).  It uses PCM's
> stream lock for protecting against the race for the state transition,
> and the trigger callback checks the state, aborts immediately if there
> is a concurrent operation.  At the exit of state change, it invokes
> the delayed trigger.
> 
> Let me know if this really works as imagined.  Then I'll cook up a
> proper patch.
> 

I'll give the patch a try tomorrow and it would be good for Marvell to
test tomorrow too (as my system does not XRUN).

Thanks

Liam

> 
> Takashi
> 
> ---
> diff --git a/include/sound/soc-dpcm.h b/include/sound/soc-dpcm.h
> index 2883a7a6f9f3..98f2ade0266e 100644
> --- a/include/sound/soc-dpcm.h
> +++ b/include/sound/soc-dpcm.h
> @@ -102,6 +102,8 @@ struct snd_soc_dpcm_runtime {
>  	/* state and update */
>  	enum snd_soc_dpcm_update runtime_update;
>  	enum snd_soc_dpcm_state state;
> +
> +	int trigger_pending; /* trigger cmd + 1 if pending, 0 if not */
>  };
>  
>  /* can this BE stop and free */
> diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c
> index 002311afdeaa..fbd570c55a67 100644
> --- a/sound/soc/soc-pcm.c
> +++ b/sound/soc/soc-pcm.c
> @@ -1522,13 +1522,36 @@ static void dpcm_set_fe_runtime(struct snd_pcm_substream *substream)
>  		dpcm_init_runtime_hw(runtime, &cpu_dai_drv->capture);
>  }
>  
> +static int dpcm_fe_dai_do_trigger(struct snd_pcm_substream *substream, int cmd);
> +
> +/* Set FE's runtime_update state; the state is protected via PCM stream lock
> + * for avoiding the race with trigger callback.
> + * If the state is unset and a trigger is pending while the previous operation,
> + * process the pending trigger action here.
> + */
> +static void dpcm_set_fe_update_state(struct snd_soc_pcm_runtime *fe,
> +				     int stream, enum snd_soc_dpcm_update state)
> +{
> +	struct snd_pcm_substream *substream =
> +		snd_soc_dpcm_get_substream(fe, stream);
> +
> +	snd_pcm_stream_lock_irq(substream);
> +	fe->dpcm[stream].runtime_update = state;
> +	if (state == SND_SOC_DPCM_UPDATE_NO && fe->dpcm[stream].trigger_pending) {
> +		dpcm_fe_dai_do_trigger(substream,
> +				       fe->dpcm[stream].trigger_pending - 1);
> +		fe->dpcm[stream].trigger_pending = 0;
> +	}
> +	snd_pcm_stream_unlock_irq(substream);
> +}
> +
>  static int dpcm_fe_dai_startup(struct snd_pcm_substream *fe_substream)
>  {
>  	struct snd_soc_pcm_runtime *fe = fe_substream->private_data;
>  	struct snd_pcm_runtime *runtime = fe_substream->runtime;
>  	int stream = fe_substream->stream, ret = 0;
>  
> -	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE;
> +	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE);
>  
>  	ret = dpcm_be_dai_startup(fe, fe_substream->stream);
>  	if (ret < 0) {
> @@ -1550,13 +1573,13 @@ static int dpcm_fe_dai_startup(struct snd_pcm_substream *fe_substream)
>  	dpcm_set_fe_runtime(fe_substream);
>  	snd_pcm_limit_hw_rates(runtime);
>  
> -	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO;
> +	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO);
>  	return 0;
>  
>  unwind:
>  	dpcm_be_dai_startup_unwind(fe, fe_substream->stream);
>  be_err:
> -	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO;
> +	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO);
>  	return ret;
>  }
>  
> @@ -1603,7 +1626,7 @@ static int dpcm_fe_dai_shutdown(struct snd_pcm_substream *substream)
>  	struct snd_soc_pcm_runtime *fe = substream->private_data;
>  	int stream = substream->stream;
>  
> -	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE;
> +	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE);
>  
>  	/* shutdown the BEs */
>  	dpcm_be_dai_shutdown(fe, substream->stream);
> @@ -1617,7 +1640,7 @@ static int dpcm_fe_dai_shutdown(struct snd_pcm_substream *substream)
>  	dpcm_dapm_stream_event(fe, stream, SND_SOC_DAPM_STREAM_STOP);
>  
>  	fe->dpcm[stream].state = SND_SOC_DPCM_STATE_CLOSE;
> -	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO;
> +	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO);
>  	return 0;
>  }
>  
> @@ -1665,7 +1688,7 @@ static int dpcm_fe_dai_hw_free(struct snd_pcm_substream *substream)
>  	int err, stream = substream->stream;
>  
>  	mutex_lock_nested(&fe->card->mutex, SND_SOC_CARD_CLASS_RUNTIME);
> -	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE;
> +	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE);
>  
>  	dev_dbg(fe->dev, "ASoC: hw_free FE %s\n", fe->dai_link->name);
>  
> @@ -1680,7 +1703,7 @@ static int dpcm_fe_dai_hw_free(struct snd_pcm_substream *substream)
>  	err = dpcm_be_dai_hw_free(fe, stream);
>  
>  	fe->dpcm[stream].state = SND_SOC_DPCM_STATE_HW_FREE;
> -	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO;
> +	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO);
>  
>  	mutex_unlock(&fe->card->mutex);
>  	return 0;
> @@ -1773,7 +1796,7 @@ static int dpcm_fe_dai_hw_params(struct snd_pcm_substream *substream,
>  	int ret, stream = substream->stream;
>  
>  	mutex_lock_nested(&fe->card->mutex, SND_SOC_CARD_CLASS_RUNTIME);
> -	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE;
> +	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE);
>  
>  	memcpy(&fe->dpcm[substream->stream].hw_params, params,
>  			sizeof(struct snd_pcm_hw_params));
> @@ -1796,7 +1819,7 @@ static int dpcm_fe_dai_hw_params(struct snd_pcm_substream *substream,
>  		fe->dpcm[stream].state = SND_SOC_DPCM_STATE_HW_PARAMS;
>  
>  out:
> -	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO;
> +	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO);
>  	mutex_unlock(&fe->card->mutex);
>  	return ret;
>  }
> @@ -1910,14 +1933,12 @@ int dpcm_be_dai_trigger(struct snd_soc_pcm_runtime *fe, int stream,
>  }
>  EXPORT_SYMBOL_GPL(dpcm_be_dai_trigger);
>  
> -static int dpcm_fe_dai_trigger(struct snd_pcm_substream *substream, int cmd)
> +static int dpcm_fe_dai_do_trigger(struct snd_pcm_substream *substream, int cmd)
>  {
>  	struct snd_soc_pcm_runtime *fe = substream->private_data;
>  	int stream = substream->stream, ret;
>  	enum snd_soc_dpcm_trigger trigger = fe->dai_link->trigger[stream];
>  
> -	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE;
> -
>  	switch (trigger) {
>  	case SND_SOC_DPCM_TRIGGER_PRE:
>  		/* call trigger on the frontend before the backend. */
> @@ -1928,7 +1949,7 @@ static int dpcm_fe_dai_trigger(struct snd_pcm_substream *substream, int cmd)
>  		ret = soc_pcm_trigger(substream, cmd);
>  		if (ret < 0) {
>  			dev_err(fe->dev,"ASoC: trigger FE failed %d\n", ret);
> -			goto out;
> +			return ret;
>  		}
>  
>  		ret = dpcm_be_dai_trigger(fe, substream->stream, cmd);
> @@ -1939,7 +1960,7 @@ static int dpcm_fe_dai_trigger(struct snd_pcm_substream *substream, int cmd)
>  		ret = dpcm_be_dai_trigger(fe, substream->stream, cmd);
>  		if (ret < 0) {
>  			dev_err(fe->dev,"ASoC: trigger FE failed %d\n", ret);
> -			goto out;
> +			return ret;
>  		}
>  
>  		dev_dbg(fe->dev, "ASoC: post trigger FE %s cmd %d\n",
> @@ -1956,14 +1977,13 @@ static int dpcm_fe_dai_trigger(struct snd_pcm_substream *substream, int cmd)
>  		ret = soc_pcm_bespoke_trigger(substream, cmd);
>  		if (ret < 0) {
>  			dev_err(fe->dev,"ASoC: trigger FE failed %d\n", ret);
> -			goto out;
> +			return ret;
>  		}
>  		break;
>  	default:
>  		dev_err(fe->dev, "ASoC: invalid trigger cmd %d for %s\n", cmd,
>  				fe->dai_link->name);
> -		ret = -EINVAL;
> -		goto out;
> +		return -EINVAL;
>  	}
>  
>  	switch (cmd) {
> @@ -1979,7 +1999,25 @@ static int dpcm_fe_dai_trigger(struct snd_pcm_substream *substream, int cmd)
>  		break;
>  	}
>  
> -out:
> +	return 0;
> +}
> +
> +static int dpcm_fe_dai_trigger(struct snd_pcm_substream *substream, int cmd)
> +{
> +	struct snd_soc_pcm_runtime *fe = substream->private_data;
> +	int stream = substream->stream, ret;
> +
> +	/* if FE's runtime_update is already set, we're in race;
> +	 * process this trigger later at exit
> +	 */
> +	if (fe->dpcm[stream].runtime_update != SND_SOC_DPCM_UPDATE_NO) {
> +		fe->dpcm[stream].trigger_pending = cmd + 1;
> +		return 0; /* delayed, assuming it's successful */
> +	}
> +
> +	/* we're alone, let's trigger */
> +	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE;
> +	ret = dpcm_fe_dai_do_trigger(substream, cmd);
>  	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO;
>  	return ret;
>  }
> @@ -2027,7 +2065,7 @@ static int dpcm_fe_dai_prepare(struct snd_pcm_substream *substream)
>  
>  	dev_dbg(fe->dev, "ASoC: prepare FE %s\n", fe->dai_link->name);
>  
> -	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE;
> +	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE);
>  
>  	/* there is no point preparing this FE if there are no BEs */
>  	if (list_empty(&fe->dpcm[stream].be_clients)) {
> @@ -2054,7 +2092,7 @@ static int dpcm_fe_dai_prepare(struct snd_pcm_substream *substream)
>  	fe->dpcm[stream].state = SND_SOC_DPCM_STATE_PREPARE;
>  
>  out:
> -	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO;
> +	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO);
>  	mutex_unlock(&fe->card->mutex);
>  
>  	return ret;
> @@ -2201,11 +2239,11 @@ static int dpcm_run_new_update(struct snd_soc_pcm_runtime *fe, int stream)
>  {
>  	int ret;
>  
> -	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_BE;
> +	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_BE);
>  	ret = dpcm_run_update_startup(fe, stream);
>  	if (ret < 0)
>  		dev_err(fe->dev, "ASoC: failed to startup some BEs\n");
> -	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO;
> +	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO);
>  
>  	return ret;
>  }
> @@ -2214,11 +2252,11 @@ static int dpcm_run_old_update(struct snd_soc_pcm_runtime *fe, int stream)
>  {
>  	int ret;
>  
> -	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_BE;
> +	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_BE);
>  	ret = dpcm_run_update_shutdown(fe, stream);
>  	if (ret < 0)
>  		dev_err(fe->dev, "ASoC: failed to shutdown some BEs\n");
> -	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO;
> +	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO);
>  
>  	return ret;
>  }
Qiao Zhou Nov. 4, 2014, 6:43 a.m. UTC | #2
>-----Original Message-----

>From: Liam Girdwood [mailto:liam.r.girdwood@linux.intel.com]

>Sent: 2014?11?4? 1:20

>To: Takashi Iwai

>Cc: Qiao Zhou; Mark Brown; perex@perex.cz; alsa-devel@alsa-project.org;

>Wilbur Wang; Chao Xie

>Subject: Re: [Question about DPCM] dpcm_run_new_update races again xrun

>

>On Mon, 2014-11-03 at 17:54 +0100, Takashi Iwai wrote:

>> At Mon, 03 Nov 2014 15:42:16 +0100,

>> Takashi Iwai wrote:

>> >

>> > At Mon, 03 Nov 2014 13:32:04 +0000,

>> > Liam Girdwood wrote:

>> > >

>> > > On Mon, 2014-11-03 at 03:28 -0800, Qiao Zhou wrote:

>> > > > Hi Mark, Liam

>> > > >

>> > > > I met one BE not-function issue when developing DPCM feature,

>> > > > and found dpcm_run_new_update is not protected well against

>xrun(interrupt context).

>> > > > Could you help to give some suggestions?

>> > > >

>> > >

>> > > I'm wondering if this would be better solved by improving the

>> > > locking so that an XRUN could not run at the same time as the

>> > > runtime update. Both functions are async, but are only protected

>> > > by a mutex atm (like the rest of PCM ops except trigger which is

>> > > atomic). We maybe need to add a spinlock to both runtime_update()

>> > > and dpcm_fe_dai_trigger()

>> >

>> > Be careful, you cannot take spinlock while hw_params, for example.

>> >

>> > Why do you need to set runtime_update to NO in the trigger callback

>> > in the first place?  The trigger may influence on the FE streaming

>> > state, but not on the FE/BE connection state, right?

>>

>> Thinking of this again, this doesn't look like a good suggestion,

>> either.

>>

>> As mentioned, we can't take a lock for the whole lengthy operation

>> like prepare or hw_params.  So, instead of taking a lock, the trigger

>> should be delayed when some operation is being performed.  (I thought

>> at first just performing FE's trigger and delaying BE later, but FE/BE

>> trigger can be more complex than that, so it ends up with the whole

>> delayed trigger.)

>>

>> The patch below is a quick hack (only compile tested!).  It uses PCM's

>> stream lock for protecting against the race for the state transition,

>> and the trigger callback checks the state, aborts immediately if there

>> is a concurrent operation.  At the exit of state change, it invokes

>> the delayed trigger.

>>

>> Let me know if this really works as imagined.  Then I'll cook up a

>> proper patch.

>>

>

>I'll give the patch a try tomorrow and it would be good for Marvell to

>test tomorrow too (as my system does not XRUN).

>

>Thanks

>

>Liam

>

>>

>> Takashi

>>

>> ---

>> diff --git a/include/sound/soc-dpcm.h b/include/sound/soc-dpcm.h index

>> 2883a7a6f9f3..98f2ade0266e 100644

>> --- a/include/sound/soc-dpcm.h

>> +++ b/include/sound/soc-dpcm.h

>> @@ -102,6 +102,8 @@ struct snd_soc_dpcm_runtime {

>>  	/* state and update */

>>  	enum snd_soc_dpcm_update runtime_update;

>>  	enum snd_soc_dpcm_state state;

>> +

>> +	int trigger_pending; /* trigger cmd + 1 if pending, 0 if not */

>>  };

>>

>>  /* can this BE stop and free */

>> diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index

>> 002311afdeaa..fbd570c55a67 100644

>> --- a/sound/soc/soc-pcm.c

>> +++ b/sound/soc/soc-pcm.c

>> @@ -1522,13 +1522,36 @@ static void dpcm_set_fe_runtime(struct

>snd_pcm_substream *substream)

>>  		dpcm_init_runtime_hw(runtime, &cpu_dai_drv->capture);  }

>>

>> +static int dpcm_fe_dai_do_trigger(struct snd_pcm_substream

>> +*substream, int cmd);

>> +

>> +/* Set FE's runtime_update state; the state is protected via PCM

>> +stream lock

>> + * for avoiding the race with trigger callback.

>> + * If the state is unset and a trigger is pending while the previous

>> +operation,

>> + * process the pending trigger action here.

>> + */

>> +static void dpcm_set_fe_update_state(struct snd_soc_pcm_runtime *fe,

>> +				     int stream, enum snd_soc_dpcm_update state)

>{

>> +	struct snd_pcm_substream *substream =

>> +		snd_soc_dpcm_get_substream(fe, stream);

>> +

>> +	snd_pcm_stream_lock_irq(substream);

>> +	fe->dpcm[stream].runtime_update = state;

>> +	if (state == SND_SOC_DPCM_UPDATE_NO && fe-

>>dpcm[stream].trigger_pending) {

>> +		dpcm_fe_dai_do_trigger(substream,

>> +				       fe->dpcm[stream].trigger_pending - 1);

>> +		fe->dpcm[stream].trigger_pending = 0;

>> +	}

>> +	snd_pcm_stream_unlock_irq(substream);

>> +}

>> +

>>  static int dpcm_fe_dai_startup(struct snd_pcm_substream

>> *fe_substream)  {

>>  	struct snd_soc_pcm_runtime *fe = fe_substream->private_data;

>>  	struct snd_pcm_runtime *runtime = fe_substream->runtime;

>>  	int stream = fe_substream->stream, ret = 0;

>>

>> -	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE;

>> +	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE);

>>

>>  	ret = dpcm_be_dai_startup(fe, fe_substream->stream);

>>  	if (ret < 0) {

>> @@ -1550,13 +1573,13 @@ static int dpcm_fe_dai_startup(struct

>snd_pcm_substream *fe_substream)

>>  	dpcm_set_fe_runtime(fe_substream);

>>  	snd_pcm_limit_hw_rates(runtime);

>>

>> -	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO;

>> +	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO);

>>  	return 0;

>>

>>  unwind:

>>  	dpcm_be_dai_startup_unwind(fe, fe_substream->stream);

>>  be_err:

>> -	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO;

>> +	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO);

>>  	return ret;

>>  }

>>

>> @@ -1603,7 +1626,7 @@ static int dpcm_fe_dai_shutdown(struct

>snd_pcm_substream *substream)

>>  	struct snd_soc_pcm_runtime *fe = substream->private_data;

>>  	int stream = substream->stream;

>>

>> -	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE;

>> +	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE);

>>

>>  	/* shutdown the BEs */

>>  	dpcm_be_dai_shutdown(fe, substream->stream); @@ -1617,7 +1640,7 @@

>> static int dpcm_fe_dai_shutdown(struct snd_pcm_substream *substream)

>>  	dpcm_dapm_stream_event(fe, stream, SND_SOC_DAPM_STREAM_STOP);

>>

>>  	fe->dpcm[stream].state = SND_SOC_DPCM_STATE_CLOSE;

>> -	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO;

>> +	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO);

>>  	return 0;

>>  }

>>

>> @@ -1665,7 +1688,7 @@ static int dpcm_fe_dai_hw_free(struct

>snd_pcm_substream *substream)

>>  	int err, stream = substream->stream;

>>

>>  	mutex_lock_nested(&fe->card->mutex, SND_SOC_CARD_CLASS_RUNTIME);

>> -	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE;

>> +	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE);

>>

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

>>

>> @@ -1680,7 +1703,7 @@ static int dpcm_fe_dai_hw_free(struct

>snd_pcm_substream *substream)

>>  	err = dpcm_be_dai_hw_free(fe, stream);

>>

>>  	fe->dpcm[stream].state = SND_SOC_DPCM_STATE_HW_FREE;

>> -	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO;

>> +	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO);

>>

>>  	mutex_unlock(&fe->card->mutex);

>>  	return 0;

>> @@ -1773,7 +1796,7 @@ static int dpcm_fe_dai_hw_params(struct

>snd_pcm_substream *substream,

>>  	int ret, stream = substream->stream;

>>

>>  	mutex_lock_nested(&fe->card->mutex, SND_SOC_CARD_CLASS_RUNTIME);

>> -	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE;

>> +	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE);

>>

>>  	memcpy(&fe->dpcm[substream->stream].hw_params, params,

>>  			sizeof(struct snd_pcm_hw_params)); @@ -1796,7 +1819,7

>@@ static

>> int dpcm_fe_dai_hw_params(struct snd_pcm_substream *substream,

>>  		fe->dpcm[stream].state = SND_SOC_DPCM_STATE_HW_PARAMS;

>>

>>  out:

>> -	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO;

>> +	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO);

>>  	mutex_unlock(&fe->card->mutex);

>>  	return ret;

>>  }

>> @@ -1910,14 +1933,12 @@ int dpcm_be_dai_trigger(struct

>> snd_soc_pcm_runtime *fe, int stream,  }

>> EXPORT_SYMBOL_GPL(dpcm_be_dai_trigger);

>>

>> -static int dpcm_fe_dai_trigger(struct snd_pcm_substream *substream,

>> int cmd)

>> +static int dpcm_fe_dai_do_trigger(struct snd_pcm_substream

>> +*substream, int cmd)

>>  {

>>  	struct snd_soc_pcm_runtime *fe = substream->private_data;

>>  	int stream = substream->stream, ret;

>>  	enum snd_soc_dpcm_trigger trigger = fe->dai_link->trigger[stream];

>>

>> -	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE;

>> -

>>  	switch (trigger) {

>>  	case SND_SOC_DPCM_TRIGGER_PRE:

>>  		/* call trigger on the frontend before the backend. */ @@ -

>1928,7

>> +1949,7 @@ static int dpcm_fe_dai_trigger(struct snd_pcm_substream

>*substream, int cmd)

>>  		ret = soc_pcm_trigger(substream, cmd);

>>  		if (ret < 0) {

>>  			dev_err(fe->dev,"ASoC: trigger FE failed %d\n", ret);

>> -			goto out;

>> +			return ret;

>>  		}

>>

>>  		ret = dpcm_be_dai_trigger(fe, substream->stream, cmd); @@ -

>1939,7

>> +1960,7 @@ static int dpcm_fe_dai_trigger(struct snd_pcm_substream

>*substream, int cmd)

>>  		ret = dpcm_be_dai_trigger(fe, substream->stream, cmd);

>>  		if (ret < 0) {

>>  			dev_err(fe->dev,"ASoC: trigger FE failed %d\n", ret);

>> -			goto out;

>> +			return ret;

>>  		}

>>

>>  		dev_dbg(fe->dev, "ASoC: post trigger FE %s cmd %d\n", @@ -

>1956,14

>> +1977,13 @@ static int dpcm_fe_dai_trigger(struct snd_pcm_substream

>*substream, int cmd)

>>  		ret = soc_pcm_bespoke_trigger(substream, cmd);

>>  		if (ret < 0) {

>>  			dev_err(fe->dev,"ASoC: trigger FE failed %d\n", ret);

>> -			goto out;

>> +			return ret;

>>  		}

>>  		break;

>>  	default:

>>  		dev_err(fe->dev, "ASoC: invalid trigger cmd %d for %s\n",

>cmd,

>>  				fe->dai_link->name);

>> -		ret = -EINVAL;

>> -		goto out;

>> +		return -EINVAL;

>>  	}

>>

>>  	switch (cmd) {

>> @@ -1979,7 +1999,25 @@ static int dpcm_fe_dai_trigger(struct

>snd_pcm_substream *substream, int cmd)

>>  		break;

>>  	}

>>

>> -out:

>> +	return 0;

>> +}

>> +

>> +static int dpcm_fe_dai_trigger(struct snd_pcm_substream *substream,

>> +int cmd) {

>> +	struct snd_soc_pcm_runtime *fe = substream->private_data;

>> +	int stream = substream->stream, ret;

>> +

>> +	/* if FE's runtime_update is already set, we're in race;

>> +	 * process this trigger later at exit

>> +	 */

>> +	if (fe->dpcm[stream].runtime_update != SND_SOC_DPCM_UPDATE_NO) {

>> +		fe->dpcm[stream].trigger_pending = cmd + 1;

>> +		return 0; /* delayed, assuming it's successful */

>> +	}

>> +

>> +	/* we're alone, let's trigger */

>> +	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE;

>> +	ret = dpcm_fe_dai_do_trigger(substream, cmd);

>>  	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO;

>>  	return ret;

>>  }

>> @@ -2027,7 +2065,7 @@ static int dpcm_fe_dai_prepare(struct

>> snd_pcm_substream *substream)

>>

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

>>

>> -	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE;

>> +	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE);

>>

>>  	/* there is no point preparing this FE if there are no BEs */

>>  	if (list_empty(&fe->dpcm[stream].be_clients)) { @@ -2054,7 +2092,7

>> @@ static int dpcm_fe_dai_prepare(struct snd_pcm_substream *substream)

>>  	fe->dpcm[stream].state = SND_SOC_DPCM_STATE_PREPARE;

>>

>>  out:

>> -	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO;

>> +	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO);

>>  	mutex_unlock(&fe->card->mutex);

>>

>>  	return ret;

>> @@ -2201,11 +2239,11 @@ static int dpcm_run_new_update(struct

>> snd_soc_pcm_runtime *fe, int stream)  {

>>  	int ret;

>>

>> -	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_BE;

>> +	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_BE);

>>  	ret = dpcm_run_update_startup(fe, stream);

>>  	if (ret < 0)

>>  		dev_err(fe->dev, "ASoC: failed to startup some BEs\n");

>> -	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO;

>> +	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO);

>>

>>  	return ret;

>>  }

>> @@ -2214,11 +2252,11 @@ static int dpcm_run_old_update(struct

>> snd_soc_pcm_runtime *fe, int stream)  {

>>  	int ret;

>>

>> -	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_BE;

>> +	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_BE);

>>  	ret = dpcm_run_update_shutdown(fe, stream);

>>  	if (ret < 0)

>>  		dev_err(fe->dev, "ASoC: failed to shutdown some BEs\n");

>> -	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO;

>> +	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO);

>>

>>  	return ret;

>>  }

>

Takashi, Liam

We have verified again and This patch fixes the race issue. Thanks a 
lot for your patch and suggestions.

Best Regards
Qiao
diff mbox

Patch

diff --git a/include/sound/soc-dpcm.h b/include/sound/soc-dpcm.h
index 2883a7a6f9f3..98f2ade0266e 100644
--- a/include/sound/soc-dpcm.h
+++ b/include/sound/soc-dpcm.h
@@ -102,6 +102,8 @@  struct snd_soc_dpcm_runtime {
 	/* state and update */
 	enum snd_soc_dpcm_update runtime_update;
 	enum snd_soc_dpcm_state state;
+
+	int trigger_pending; /* trigger cmd + 1 if pending, 0 if not */
 };
 
 /* can this BE stop and free */
diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c
index 002311afdeaa..fbd570c55a67 100644
--- a/sound/soc/soc-pcm.c
+++ b/sound/soc/soc-pcm.c
@@ -1522,13 +1522,36 @@  static void dpcm_set_fe_runtime(struct snd_pcm_substream *substream)
 		dpcm_init_runtime_hw(runtime, &cpu_dai_drv->capture);
 }
 
+static int dpcm_fe_dai_do_trigger(struct snd_pcm_substream *substream, int cmd);
+
+/* Set FE's runtime_update state; the state is protected via PCM stream lock
+ * for avoiding the race with trigger callback.
+ * If the state is unset and a trigger is pending while the previous operation,
+ * process the pending trigger action here.
+ */
+static void dpcm_set_fe_update_state(struct snd_soc_pcm_runtime *fe,
+				     int stream, enum snd_soc_dpcm_update state)
+{
+	struct snd_pcm_substream *substream =
+		snd_soc_dpcm_get_substream(fe, stream);
+
+	snd_pcm_stream_lock_irq(substream);
+	fe->dpcm[stream].runtime_update = state;
+	if (state == SND_SOC_DPCM_UPDATE_NO && fe->dpcm[stream].trigger_pending) {
+		dpcm_fe_dai_do_trigger(substream,
+				       fe->dpcm[stream].trigger_pending - 1);
+		fe->dpcm[stream].trigger_pending = 0;
+	}
+	snd_pcm_stream_unlock_irq(substream);
+}
+
 static int dpcm_fe_dai_startup(struct snd_pcm_substream *fe_substream)
 {
 	struct snd_soc_pcm_runtime *fe = fe_substream->private_data;
 	struct snd_pcm_runtime *runtime = fe_substream->runtime;
 	int stream = fe_substream->stream, ret = 0;
 
-	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE;
+	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE);
 
 	ret = dpcm_be_dai_startup(fe, fe_substream->stream);
 	if (ret < 0) {
@@ -1550,13 +1573,13 @@  static int dpcm_fe_dai_startup(struct snd_pcm_substream *fe_substream)
 	dpcm_set_fe_runtime(fe_substream);
 	snd_pcm_limit_hw_rates(runtime);
 
-	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO;
+	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO);
 	return 0;
 
 unwind:
 	dpcm_be_dai_startup_unwind(fe, fe_substream->stream);
 be_err:
-	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO;
+	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO);
 	return ret;
 }
 
@@ -1603,7 +1626,7 @@  static int dpcm_fe_dai_shutdown(struct snd_pcm_substream *substream)
 	struct snd_soc_pcm_runtime *fe = substream->private_data;
 	int stream = substream->stream;
 
-	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE;
+	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE);
 
 	/* shutdown the BEs */
 	dpcm_be_dai_shutdown(fe, substream->stream);
@@ -1617,7 +1640,7 @@  static int dpcm_fe_dai_shutdown(struct snd_pcm_substream *substream)
 	dpcm_dapm_stream_event(fe, stream, SND_SOC_DAPM_STREAM_STOP);
 
 	fe->dpcm[stream].state = SND_SOC_DPCM_STATE_CLOSE;
-	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO;
+	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO);
 	return 0;
 }
 
@@ -1665,7 +1688,7 @@  static int dpcm_fe_dai_hw_free(struct snd_pcm_substream *substream)
 	int err, stream = substream->stream;
 
 	mutex_lock_nested(&fe->card->mutex, SND_SOC_CARD_CLASS_RUNTIME);
-	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE;
+	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE);
 
 	dev_dbg(fe->dev, "ASoC: hw_free FE %s\n", fe->dai_link->name);
 
@@ -1680,7 +1703,7 @@  static int dpcm_fe_dai_hw_free(struct snd_pcm_substream *substream)
 	err = dpcm_be_dai_hw_free(fe, stream);
 
 	fe->dpcm[stream].state = SND_SOC_DPCM_STATE_HW_FREE;
-	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO;
+	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO);
 
 	mutex_unlock(&fe->card->mutex);
 	return 0;
@@ -1773,7 +1796,7 @@  static int dpcm_fe_dai_hw_params(struct snd_pcm_substream *substream,
 	int ret, stream = substream->stream;
 
 	mutex_lock_nested(&fe->card->mutex, SND_SOC_CARD_CLASS_RUNTIME);
-	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE;
+	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE);
 
 	memcpy(&fe->dpcm[substream->stream].hw_params, params,
 			sizeof(struct snd_pcm_hw_params));
@@ -1796,7 +1819,7 @@  static int dpcm_fe_dai_hw_params(struct snd_pcm_substream *substream,
 		fe->dpcm[stream].state = SND_SOC_DPCM_STATE_HW_PARAMS;
 
 out:
-	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO;
+	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO);
 	mutex_unlock(&fe->card->mutex);
 	return ret;
 }
@@ -1910,14 +1933,12 @@  int dpcm_be_dai_trigger(struct snd_soc_pcm_runtime *fe, int stream,
 }
 EXPORT_SYMBOL_GPL(dpcm_be_dai_trigger);
 
-static int dpcm_fe_dai_trigger(struct snd_pcm_substream *substream, int cmd)
+static int dpcm_fe_dai_do_trigger(struct snd_pcm_substream *substream, int cmd)
 {
 	struct snd_soc_pcm_runtime *fe = substream->private_data;
 	int stream = substream->stream, ret;
 	enum snd_soc_dpcm_trigger trigger = fe->dai_link->trigger[stream];
 
-	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE;
-
 	switch (trigger) {
 	case SND_SOC_DPCM_TRIGGER_PRE:
 		/* call trigger on the frontend before the backend. */
@@ -1928,7 +1949,7 @@  static int dpcm_fe_dai_trigger(struct snd_pcm_substream *substream, int cmd)
 		ret = soc_pcm_trigger(substream, cmd);
 		if (ret < 0) {
 			dev_err(fe->dev,"ASoC: trigger FE failed %d\n", ret);
-			goto out;
+			return ret;
 		}
 
 		ret = dpcm_be_dai_trigger(fe, substream->stream, cmd);
@@ -1939,7 +1960,7 @@  static int dpcm_fe_dai_trigger(struct snd_pcm_substream *substream, int cmd)
 		ret = dpcm_be_dai_trigger(fe, substream->stream, cmd);
 		if (ret < 0) {
 			dev_err(fe->dev,"ASoC: trigger FE failed %d\n", ret);
-			goto out;
+			return ret;
 		}
 
 		dev_dbg(fe->dev, "ASoC: post trigger FE %s cmd %d\n",
@@ -1956,14 +1977,13 @@  static int dpcm_fe_dai_trigger(struct snd_pcm_substream *substream, int cmd)
 		ret = soc_pcm_bespoke_trigger(substream, cmd);
 		if (ret < 0) {
 			dev_err(fe->dev,"ASoC: trigger FE failed %d\n", ret);
-			goto out;
+			return ret;
 		}
 		break;
 	default:
 		dev_err(fe->dev, "ASoC: invalid trigger cmd %d for %s\n", cmd,
 				fe->dai_link->name);
-		ret = -EINVAL;
-		goto out;
+		return -EINVAL;
 	}
 
 	switch (cmd) {
@@ -1979,7 +1999,25 @@  static int dpcm_fe_dai_trigger(struct snd_pcm_substream *substream, int cmd)
 		break;
 	}
 
-out:
+	return 0;
+}
+
+static int dpcm_fe_dai_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_soc_pcm_runtime *fe = substream->private_data;
+	int stream = substream->stream, ret;
+
+	/* if FE's runtime_update is already set, we're in race;
+	 * process this trigger later at exit
+	 */
+	if (fe->dpcm[stream].runtime_update != SND_SOC_DPCM_UPDATE_NO) {
+		fe->dpcm[stream].trigger_pending = cmd + 1;
+		return 0; /* delayed, assuming it's successful */
+	}
+
+	/* we're alone, let's trigger */
+	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE;
+	ret = dpcm_fe_dai_do_trigger(substream, cmd);
 	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO;
 	return ret;
 }
@@ -2027,7 +2065,7 @@  static int dpcm_fe_dai_prepare(struct snd_pcm_substream *substream)
 
 	dev_dbg(fe->dev, "ASoC: prepare FE %s\n", fe->dai_link->name);
 
-	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE;
+	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE);
 
 	/* there is no point preparing this FE if there are no BEs */
 	if (list_empty(&fe->dpcm[stream].be_clients)) {
@@ -2054,7 +2092,7 @@  static int dpcm_fe_dai_prepare(struct snd_pcm_substream *substream)
 	fe->dpcm[stream].state = SND_SOC_DPCM_STATE_PREPARE;
 
 out:
-	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO;
+	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO);
 	mutex_unlock(&fe->card->mutex);
 
 	return ret;
@@ -2201,11 +2239,11 @@  static int dpcm_run_new_update(struct snd_soc_pcm_runtime *fe, int stream)
 {
 	int ret;
 
-	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_BE;
+	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_BE);
 	ret = dpcm_run_update_startup(fe, stream);
 	if (ret < 0)
 		dev_err(fe->dev, "ASoC: failed to startup some BEs\n");
-	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO;
+	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO);
 
 	return ret;
 }
@@ -2214,11 +2252,11 @@  static int dpcm_run_old_update(struct snd_soc_pcm_runtime *fe, int stream)
 {
 	int ret;
 
-	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_BE;
+	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_BE);
 	ret = dpcm_run_update_shutdown(fe, stream);
 	if (ret < 0)
 		dev_err(fe->dev, "ASoC: failed to shutdown some BEs\n");
-	fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO;
+	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO);
 
 	return ret;
 }