Commit 21cb2a2e authored by Takashi Iwai's avatar Takashi Iwai Committed by Jaroslav Kysela

[ALSA] Fix races between PCM drain and other ops

PCM Midlevel
Fix semaphore races between PCM drain and other ops.
Signed-off-by: default avatarTakashi Iwai <tiwai@suse.de>
parent ce43fbae
...@@ -1368,43 +1368,32 @@ static int snd_pcm_drain(snd_pcm_substream_t *substream) ...@@ -1368,43 +1368,32 @@ static int snd_pcm_drain(snd_pcm_substream_t *substream)
if (runtime->status->state == SNDRV_PCM_STATE_OPEN) if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
return -EBADFD; return -EBADFD;
down_read(&snd_pcm_link_rwsem);
snd_power_lock(card); snd_power_lock(card);
if (runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) { if (runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) {
result = snd_power_wait(card, SNDRV_CTL_POWER_D0, substream->ffile); result = snd_power_wait(card, SNDRV_CTL_POWER_D0, substream->ffile);
if (result < 0) if (result < 0) {
goto _unlock; snd_power_unlock(card);
return result;
}
} }
/* allocate temporary record for drain sync */ /* allocate temporary record for drain sync */
down_read(&snd_pcm_link_rwsem);
if (snd_pcm_stream_linked(substream)) { if (snd_pcm_stream_linked(substream)) {
drec = kmalloc(substream->group->count * sizeof(*drec), GFP_KERNEL); drec = kmalloc(substream->group->count * sizeof(*drec), GFP_KERNEL);
if (! drec) { if (! drec) {
result = -ENOMEM; up_read(&snd_pcm_link_rwsem);
goto _unlock; snd_power_unlock(card);
return -ENOMEM;
} }
} else } else
drec = &drec_tmp; drec = &drec_tmp;
snd_pcm_stream_lock_irq(substream); /* count only playback streams */
/* resume pause */
if (runtime->status->state == SNDRV_PCM_STATE_PAUSED)
snd_pcm_pause(substream, 0);
/* pre-start/stop - all running streams are changed to DRAINING state */
result = snd_pcm_action(&snd_pcm_action_drain_init, substream, 0);
if (result < 0)
goto _end;
/* check streams with PLAYBACK & DRAINING */
num_drecs = 0; num_drecs = 0;
snd_pcm_group_for_each(pos, substream) { snd_pcm_group_for_each(pos, substream) {
snd_pcm_substream_t *s = snd_pcm_group_substream_entry(pos); snd_pcm_substream_t *s = snd_pcm_group_substream_entry(pos);
runtime = s->runtime; runtime = s->runtime;
if (runtime->status->state != SNDRV_PCM_STATE_DRAINING) {
runtime->status->state = SNDRV_PCM_STATE_SETUP;
continue;
}
if (s->stream == SNDRV_PCM_STREAM_PLAYBACK) { if (s->stream == SNDRV_PCM_STREAM_PLAYBACK) {
d = &drec[num_drecs++]; d = &drec[num_drecs++];
d->substream = s; d->substream = s;
...@@ -1418,9 +1407,21 @@ static int snd_pcm_drain(snd_pcm_substream_t *substream) ...@@ -1418,9 +1407,21 @@ static int snd_pcm_drain(snd_pcm_substream_t *substream)
runtime->stop_threshold = runtime->buffer_size; runtime->stop_threshold = runtime->buffer_size;
} }
} }
up_read(&snd_pcm_link_rwsem);
if (! num_drecs) if (! num_drecs)
goto _end; goto _error;
snd_pcm_stream_lock_irq(substream);
/* resume pause */
if (runtime->status->state == SNDRV_PCM_STATE_PAUSED)
snd_pcm_pause(substream, 0);
/* pre-start/stop - all running streams are changed to DRAINING state */
result = snd_pcm_action(&snd_pcm_action_drain_init, substream, 0);
if (result < 0) {
snd_pcm_stream_unlock_irq(substream);
goto _error;
}
for (;;) { for (;;) {
long tout; long tout;
...@@ -1428,6 +1429,15 @@ static int snd_pcm_drain(snd_pcm_substream_t *substream) ...@@ -1428,6 +1429,15 @@ static int snd_pcm_drain(snd_pcm_substream_t *substream)
result = -ERESTARTSYS; result = -ERESTARTSYS;
break; break;
} }
/* all finished? */
for (i = 0; i < num_drecs; i++) {
runtime = drec[i].substream->runtime;
if (runtime->status->state == SNDRV_PCM_STATE_DRAINING)
break;
}
if (i == num_drecs)
break; /* yes, all drained */
set_current_state(TASK_INTERRUPTIBLE); set_current_state(TASK_INTERRUPTIBLE);
snd_pcm_stream_unlock_irq(substream); snd_pcm_stream_unlock_irq(substream);
snd_power_unlock(card); snd_power_unlock(card);
...@@ -1444,15 +1454,11 @@ static int snd_pcm_drain(snd_pcm_substream_t *substream) ...@@ -1444,15 +1454,11 @@ static int snd_pcm_drain(snd_pcm_substream_t *substream)
} }
break; break;
} }
/* all finished? */
for (i = 0; i < num_drecs; i++) {
runtime = drec[i].substream->runtime;
if (runtime->status->state == SNDRV_PCM_STATE_DRAINING)
break;
}
if (i == num_drecs)
break;
} }
snd_pcm_stream_unlock_irq(substream);
_error:
for (i = 0; i < num_drecs; i++) { for (i = 0; i < num_drecs; i++) {
d = &drec[i]; d = &drec[i];
runtime = d->substream->runtime; runtime = d->substream->runtime;
...@@ -1460,13 +1466,9 @@ static int snd_pcm_drain(snd_pcm_substream_t *substream) ...@@ -1460,13 +1466,9 @@ static int snd_pcm_drain(snd_pcm_substream_t *substream)
runtime->stop_threshold = d->stop_threshold; runtime->stop_threshold = d->stop_threshold;
} }
_end:
snd_pcm_stream_unlock_irq(substream);
if (drec != &drec_tmp) if (drec != &drec_tmp)
kfree(drec); kfree(drec);
_unlock:
snd_power_unlock(card); snd_power_unlock(card);
up_read(&snd_pcm_link_rwsem);
return result; return result;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment