ALSA: PCM: Fix some races at disconnection
Fix races at PCM disconnection: - while a PCM device is being opened or closed - while the PCM state is being changed without lock in prepare, hw_params, hw_free ops Reported-by: Matthieu CASTET <matthieu.castet@parrot.com> Cc: <stable@vger.kernel.org> Signed-off-by: Takashi Iwai <tiwai@suse.de>
This commit is contained in:
@@ -1086,11 +1086,15 @@ static int snd_pcm_dev_disconnect(struct snd_device *device)
|
|||||||
if (list_empty(&pcm->list))
|
if (list_empty(&pcm->list))
|
||||||
goto unlock;
|
goto unlock;
|
||||||
|
|
||||||
|
mutex_lock(&pcm->open_mutex);
|
||||||
list_del_init(&pcm->list);
|
list_del_init(&pcm->list);
|
||||||
for (cidx = 0; cidx < 2; cidx++)
|
for (cidx = 0; cidx < 2; cidx++)
|
||||||
for (substream = pcm->streams[cidx].substream; substream; substream = substream->next)
|
for (substream = pcm->streams[cidx].substream; substream; substream = substream->next) {
|
||||||
|
snd_pcm_stream_lock_irq(substream);
|
||||||
if (substream->runtime)
|
if (substream->runtime)
|
||||||
substream->runtime->status->state = SNDRV_PCM_STATE_DISCONNECTED;
|
substream->runtime->status->state = SNDRV_PCM_STATE_DISCONNECTED;
|
||||||
|
snd_pcm_stream_unlock_irq(substream);
|
||||||
|
}
|
||||||
list_for_each_entry(notify, &snd_pcm_notify_list, list) {
|
list_for_each_entry(notify, &snd_pcm_notify_list, list) {
|
||||||
notify->n_disconnect(pcm);
|
notify->n_disconnect(pcm);
|
||||||
}
|
}
|
||||||
@@ -1110,6 +1114,7 @@ static int snd_pcm_dev_disconnect(struct snd_device *device)
|
|||||||
pcm->streams[cidx].chmap_kctl = NULL;
|
pcm->streams[cidx].chmap_kctl = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
mutex_unlock(&pcm->open_mutex);
|
||||||
unlock:
|
unlock:
|
||||||
mutex_unlock(®ister_mutex);
|
mutex_unlock(®ister_mutex);
|
||||||
return 0;
|
return 0;
|
||||||
|
@@ -369,6 +369,14 @@ static int period_to_usecs(struct snd_pcm_runtime *runtime)
|
|||||||
return usecs;
|
return usecs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void snd_pcm_set_state(struct snd_pcm_substream *substream, int state)
|
||||||
|
{
|
||||||
|
snd_pcm_stream_lock_irq(substream);
|
||||||
|
if (substream->runtime->status->state != SNDRV_PCM_STATE_DISCONNECTED)
|
||||||
|
substream->runtime->status->state = state;
|
||||||
|
snd_pcm_stream_unlock_irq(substream);
|
||||||
|
}
|
||||||
|
|
||||||
static int snd_pcm_hw_params(struct snd_pcm_substream *substream,
|
static int snd_pcm_hw_params(struct snd_pcm_substream *substream,
|
||||||
struct snd_pcm_hw_params *params)
|
struct snd_pcm_hw_params *params)
|
||||||
{
|
{
|
||||||
@@ -452,7 +460,7 @@ static int snd_pcm_hw_params(struct snd_pcm_substream *substream,
|
|||||||
runtime->boundary *= 2;
|
runtime->boundary *= 2;
|
||||||
|
|
||||||
snd_pcm_timer_resolution_change(substream);
|
snd_pcm_timer_resolution_change(substream);
|
||||||
runtime->status->state = SNDRV_PCM_STATE_SETUP;
|
snd_pcm_set_state(substream, SNDRV_PCM_STATE_SETUP);
|
||||||
|
|
||||||
if (pm_qos_request_active(&substream->latency_pm_qos_req))
|
if (pm_qos_request_active(&substream->latency_pm_qos_req))
|
||||||
pm_qos_remove_request(&substream->latency_pm_qos_req);
|
pm_qos_remove_request(&substream->latency_pm_qos_req);
|
||||||
@@ -464,7 +472,7 @@ static int snd_pcm_hw_params(struct snd_pcm_substream *substream,
|
|||||||
/* hardware might be unusable from this time,
|
/* hardware might be unusable from this time,
|
||||||
so we force application to retry to set
|
so we force application to retry to set
|
||||||
the correct hardware parameter settings */
|
the correct hardware parameter settings */
|
||||||
runtime->status->state = SNDRV_PCM_STATE_OPEN;
|
snd_pcm_set_state(substream, SNDRV_PCM_STATE_OPEN);
|
||||||
if (substream->ops->hw_free != NULL)
|
if (substream->ops->hw_free != NULL)
|
||||||
substream->ops->hw_free(substream);
|
substream->ops->hw_free(substream);
|
||||||
return err;
|
return err;
|
||||||
@@ -512,7 +520,7 @@ static int snd_pcm_hw_free(struct snd_pcm_substream *substream)
|
|||||||
return -EBADFD;
|
return -EBADFD;
|
||||||
if (substream->ops->hw_free)
|
if (substream->ops->hw_free)
|
||||||
result = substream->ops->hw_free(substream);
|
result = substream->ops->hw_free(substream);
|
||||||
runtime->status->state = SNDRV_PCM_STATE_OPEN;
|
snd_pcm_set_state(substream, SNDRV_PCM_STATE_OPEN);
|
||||||
pm_qos_remove_request(&substream->latency_pm_qos_req);
|
pm_qos_remove_request(&substream->latency_pm_qos_req);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -1320,7 +1328,7 @@ static void snd_pcm_post_prepare(struct snd_pcm_substream *substream, int state)
|
|||||||
{
|
{
|
||||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||||
runtime->control->appl_ptr = runtime->status->hw_ptr;
|
runtime->control->appl_ptr = runtime->status->hw_ptr;
|
||||||
runtime->status->state = SNDRV_PCM_STATE_PREPARED;
|
snd_pcm_set_state(substream, SNDRV_PCM_STATE_PREPARED);
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct action_ops snd_pcm_action_prepare = {
|
static struct action_ops snd_pcm_action_prepare = {
|
||||||
|
Reference in New Issue
Block a user