[alsa-devel] [PATCH][RFC] pcm: add exclusion between hw_params and prepare callbacks

Takashi Iwai tiwai at suse.de
Wed Nov 28 11:10:47 CET 2007


At Wed, 28 Nov 2007 02:19:48 -0500,
Dave Dillow wrote:
> 
> While working on the locking in the sis7019 driver, and as also noted by
> Trent Piepho, there is a race between threads calling into the driver's
> hw_params callback and its prepare callback. This can lead to
> incorrect/inconsistent data being programmed into the audio device, and
> lead to unexpected results, if not a possible system crash. While it is
> arguably a bug in the user space program, we should protect the system
> and provide some assurance to the driver code about concurrency between
> these two callbacks.
> 
> The attached patch adds a mutex to the two paths to provide the locking
> without giving up the ability for those calls to schedule, as documented
> in the API. It just to get a discussion going, as the code to be
> restructured a bit to clean up the error exit paths. It is also likely
> there are other callbacks that should be excluded when either of these
> are executing as well.

Thanks for raising this up.  

Your fix is applicable, but I prefer rather fixing the whole locking
stuff in pcm_native.c.  Due to the linked PCM streams, we have a huge
mess there, and there is even a potential deadlock.


Takashi


> 
> This applies to the base kernel tree.
> 
> Dave
> ---
>  include/sound/pcm.h     |    2 ++
>  sound/core/pcm.c        |    1 +
>  sound/core/pcm_native.c |   20 +++++++++++++++++---
>  3 files changed, 20 insertions(+), 3 deletions(-)
> 
> diff --git a/include/sound/pcm.h b/include/sound/pcm.h
> index 73334e0..989e84f 100644
> --- a/include/sound/pcm.h
> +++ b/include/sound/pcm.h
> @@ -388,6 +388,8 @@ struct snd_pcm_substream {
>  	struct snd_info_entry *proc_prealloc_entry;
>  	struct snd_info_entry *proc_prealloc_max_entry;
>  #endif
> +	/* -- hw_param/prepare exclusion -- */
> +	struct mutex hw_param_mutex;
>  	/* misc flags */
>  	unsigned int hw_opened: 1;
>  };
> diff --git a/sound/core/pcm.c b/sound/core/pcm.c
> index 2743414..d728594 100644
> --- a/sound/core/pcm.c
> +++ b/sound/core/pcm.c
> @@ -651,6 +651,7 @@ int snd_pcm_new_stream(struct snd_pcm *pcm, int stream, int substream_count)
>  		list_add_tail(&substream->link_list, &substream->self_group.substreams);
>  		spin_lock_init(&substream->timer_lock);
>  		atomic_set(&substream->mmap_count, 0);
> +		mutex_init(&substream->hw_param_mutex);
>  		prev = substream;
>  	}
>  	return 0;
> diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c
> index 59b29cd..638d78a 100644
> --- a/sound/core/pcm_native.c
> +++ b/sound/core/pcm_native.c
> @@ -373,6 +373,7 @@ static int snd_pcm_hw_params(struct snd_pcm_substream *substream,
>  	snd_assert(substream != NULL, return -ENXIO);
>  	runtime = substream->runtime;
>  	snd_assert(runtime != NULL, return -ENXIO);
> +	mutex_lock(&substream->hw_param_mutex);
>  	snd_pcm_stream_lock_irq(substream);
>  	switch (runtime->status->state) {
>  	case SNDRV_PCM_STATE_OPEN:
> @@ -380,6 +381,7 @@ static int snd_pcm_hw_params(struct snd_pcm_substream *substream,
>  	case SNDRV_PCM_STATE_PREPARED:
>  		break;
>  	default:
> +		mutex_unlock(&substream->hw_param_mutex);
>  		snd_pcm_stream_unlock_irq(substream);
>  		return -EBADFD;
>  	}
> @@ -387,8 +389,10 @@ static int snd_pcm_hw_params(struct snd_pcm_substream *substream,
>  #if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE)
>  	if (!substream->oss.oss)
>  #endif
> -		if (atomic_read(&substream->mmap_count))
> +		if (atomic_read(&substream->mmap_count)) {
> +			mutex_unlock(&substream->hw_param_mutex);
>  			return -EBADFD;
> +		}
>  
>  	params->rmask = ~0U;
>  	err = snd_pcm_hw_refine(substream, params);
> @@ -450,6 +454,7 @@ static int snd_pcm_hw_params(struct snd_pcm_substream *substream,
>  	remove_acceptable_latency(substream->latency_id);
>  	if ((usecs = period_to_usecs(runtime)) >= 0)
>  		set_acceptable_latency(substream->latency_id, usecs);
> +	mutex_unlock(&substream->hw_param_mutex);
>  	return 0;
>   _error:
>  	/* hardware might be unuseable from this time,
> @@ -458,6 +463,7 @@ static int snd_pcm_hw_params(struct snd_pcm_substream *substream,
>  	runtime->status->state = SNDRV_PCM_STATE_OPEN;
>  	if (substream->ops->hw_free != NULL)
>  		substream->ops->hw_free(substream);
> +	mutex_unlock(&substream->hw_param_mutex);
>  	return err;
>  }
>  
> @@ -1345,9 +1351,10 @@ static struct action_ops snd_pcm_action_prepare = {
>  static int snd_pcm_prepare(struct snd_pcm_substream *substream,
>  			   struct file *file)
>  {
> -	int res;
>  	struct snd_card *card = substream->pcm->card;
> +	struct snd_pcm_substream *s;
>  	int f_flags;
> +	int res;
>  
>  	if (file)
>  		f_flags = file->f_flags;
> @@ -1355,9 +1362,16 @@ static int snd_pcm_prepare(struct snd_pcm_substream *substream,
>  		f_flags = substream->f_flags;
>  
>  	snd_power_lock(card);
> -	if ((res = snd_power_wait(card, SNDRV_CTL_POWER_D0)) >= 0)
> +	if ((res = snd_power_wait(card, SNDRV_CTL_POWER_D0)) >= 0) {
> +		down_read(&snd_pcm_link_rwsem);
> +		snd_pcm_group_for_each_entry(s, substream)
> +			mutex_lock(&s->hw_param_mutex);
>  		res = snd_pcm_action_nonatomic(&snd_pcm_action_prepare,
>  					       substream, f_flags);
> +		snd_pcm_group_for_each_entry(s, substream)
> +			mutex_unlock(&s->hw_param_mutex);
> +		up_read(&snd_pcm_link_rwsem);
> +	}
>  	snd_power_unlock(card);
>  	return res;
>  }
> 
> _______________________________________________
> Alsa-devel mailing list
> Alsa-devel at alsa-project.org
> http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
> 


More information about the Alsa-devel mailing list