summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaroslav Kysela <perex@suse.cz>2004-10-07 15:56:30 +0200
committerJaroslav Kysela <perex@suse.cz>2004-10-07 15:56:30 +0200
commit23151bd7566a231b6d8ec32398ae133ff680aef4 (patch)
treeb861ee92185c460d9be195bb2410b96abbbccfcd
parent8d51bcbb70c06b22ad7a4af7afe8210cccb532b0 (diff)
[ALSA] Fix drain/drop of linked PCM streams
PCM Midlevel This patch fixes the dead-locking of linked PCM streams when drain/drop is called. The counter field is added to pcm group struct to handle link/unlink more easily. When the PCM streams are linked, start/drain/drop are operated to all linked streams. The drain will wait until draining of all linked streams are finished. The XRUN triggers stopping of all linked streams and changes the state of all of them to XRUN even if only one of them is actually in XRUN. Signed-off-by: Takashi Iwai <tiwai@suse.de>
-rw-r--r--include/sound/pcm.h2
-rw-r--r--sound/core/pcm_lib.c2
-rw-r--r--sound/core/pcm_native.c634
3 files changed, 323 insertions, 315 deletions
diff --git a/include/sound/pcm.h b/include/sound/pcm.h
index faf4aef8f96c..e8badb90a279 100644
--- a/include/sound/pcm.h
+++ b/include/sound/pcm.h
@@ -364,6 +364,7 @@ struct _snd_pcm_runtime {
typedef struct _snd_pcm_group { /* keep linked substreams */
spinlock_t lock;
struct list_head substreams;
+ int count;
} snd_pcm_group_t;
struct _snd_pcm_substream {
@@ -488,6 +489,7 @@ int snd_pcm_status(snd_pcm_substream_t * substream, snd_pcm_status_t *status);
int snd_pcm_prepare(snd_pcm_substream_t *substream);
int snd_pcm_start(snd_pcm_substream_t *substream);
int snd_pcm_stop(snd_pcm_substream_t *substream, int status);
+int snd_pcm_drain_done(snd_pcm_substream_t *substream);
#ifdef CONFIG_PM
int snd_pcm_suspend(snd_pcm_substream_t *substream);
int snd_pcm_suspend_all(snd_pcm_t *pcm);
diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c
index 7434645365b6..07ee3a9476f4 100644
--- a/sound/core/pcm_lib.c
+++ b/sound/core/pcm_lib.c
@@ -176,7 +176,7 @@ static inline int snd_pcm_update_hw_ptr_post(snd_pcm_substream_t *substream,
runtime->avail_max = avail;
if (avail >= runtime->stop_threshold) {
if (substream->runtime->status->state == SNDRV_PCM_STATE_DRAINING)
- snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP);
+ snd_pcm_drain_done(substream);
else
xrun(substream);
return -EPIPE;
diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c
index 29f8975e7496..1c2ddfd44809 100644
--- a/sound/core/pcm_native.c
+++ b/sound/core/pcm_native.c
@@ -451,7 +451,7 @@ out:
static int snd_pcm_hw_free(snd_pcm_substream_t * substream)
{
snd_pcm_runtime_t *runtime;
- int result;
+ int result = 0;
snd_assert(substream != NULL, return -ENXIO);
runtime = substream->runtime;
@@ -468,11 +468,8 @@ static int snd_pcm_hw_free(snd_pcm_substream_t * substream)
snd_pcm_stream_unlock_irq(substream);
if (atomic_read(&runtime->mmap_count))
return -EBADFD;
- if (substream->ops->hw_free == NULL) {
- runtime->status->state = SNDRV_PCM_STATE_OPEN;
- return 0;
- }
- result = substream->ops->hw_free(substream);
+ if (substream->ops->hw_free)
+ result = substream->ops->hw_free(substream);
runtime->status->state = SNDRV_PCM_STATE_OPEN;
return result;
}
@@ -652,6 +649,7 @@ static void snd_pcm_trigger_tstamp(snd_pcm_substream_t *substream)
struct action_ops {
int (*pre_action)(snd_pcm_substream_t *substream, int state);
int (*do_action)(snd_pcm_substream_t *substream, int state);
+ void (*undo_action)(snd_pcm_substream_t *substream, int state);
void (*post_action)(snd_pcm_substream_t *substream, int state);
};
@@ -666,7 +664,8 @@ static int snd_pcm_action_group(struct action_ops *ops,
{
struct list_head *pos;
snd_pcm_substream_t *s = NULL;
- int err, res = 0;
+ snd_pcm_substream_t *s1;
+ int res = 0;
snd_pcm_group_for_each(pos, substream) {
s = snd_pcm_group_substream_entry(pos);
@@ -674,24 +673,31 @@ static int snd_pcm_action_group(struct action_ops *ops,
spin_lock(&s->self_group.lock);
res = ops->pre_action(s, state);
if (res < 0)
- break;
+ goto _unlock;
}
- if (res >= 0) {
- snd_pcm_group_for_each(pos, substream) {
- s = snd_pcm_group_substream_entry(pos);
- err = ops->do_action(s, state);
- if (err < 0) {
- if (res == 0)
- res = err;
- } else {
- ops->post_action(s, state);
+ snd_pcm_group_for_each(pos, substream) {
+ s = snd_pcm_group_substream_entry(pos);
+ res = ops->do_action(s, state);
+ if (res < 0) {
+ if (ops->undo_action) {
+ snd_pcm_group_for_each(pos, substream) {
+ s1 = snd_pcm_group_substream_entry(pos);
+ if (s1 == s) /* failed stream */
+ break;
+ ops->undo_action(s1, state);
+ }
}
- if (do_lock && s != substream)
- spin_unlock(&s->self_group.lock);
+ s = NULL; /* unlock all */
+ goto _unlock;
}
- } else if (do_lock) {
- snd_pcm_substream_t *s1;
- /* unlock all streams */
+ }
+ snd_pcm_group_for_each(pos, substream) {
+ s = snd_pcm_group_substream_entry(pos);
+ ops->post_action(s, state);
+ }
+ _unlock:
+ if (do_lock) {
+ /* unlock streams */
snd_pcm_group_for_each(pos, substream) {
s1 = snd_pcm_group_substream_entry(pos);
if (s1 != substream)
@@ -716,9 +722,10 @@ static int snd_pcm_action_single(struct action_ops *ops,
if (res < 0)
return res;
res = ops->do_action(substream, state);
- if (res == 0) {
+ if (res == 0)
ops->post_action(substream, state);
- }
+ else if (ops->undo_action)
+ ops->undo_action(substream, state);
return res;
}
@@ -787,6 +794,9 @@ static int snd_pcm_action_nonatomic(struct action_ops *ops,
return res;
}
+/*
+ * start callbacks
+ */
static int snd_pcm_pre_start(snd_pcm_substream_t *substream, int state)
{
snd_pcm_runtime_t *runtime = substream->runtime;
@@ -803,14 +813,20 @@ static int snd_pcm_do_start(snd_pcm_substream_t *substream, int state)
{
if (substream->runtime->trigger_master != substream)
return 0;
- return substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_START);
+ return substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_START);
+}
+
+static void snd_pcm_undo_start(snd_pcm_substream_t *substream, int state)
+{
+ if (substream->runtime->trigger_master == substream)
+ substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_STOP);
}
static void snd_pcm_post_start(snd_pcm_substream_t *substream, int state)
{
snd_pcm_runtime_t *runtime = substream->runtime;
snd_pcm_trigger_tstamp(substream);
- runtime->status->state = SNDRV_PCM_STATE_RUNNING;
+ runtime->status->state = state;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
runtime->silence_size > 0)
snd_pcm_playback_silence(substream, ULONG_MAX);
@@ -823,22 +839,27 @@ static void snd_pcm_post_start(snd_pcm_substream_t *substream, int state)
static struct action_ops snd_pcm_action_start = {
.pre_action = snd_pcm_pre_start,
.do_action = snd_pcm_do_start,
+ .undo_action = snd_pcm_undo_start,
.post_action = snd_pcm_post_start
};
/**
* snd_pcm_start
+ *
+ * Start all linked streams.
*/
int snd_pcm_start(snd_pcm_substream_t *substream)
{
- return snd_pcm_action(&snd_pcm_action_start, substream, 0);
+ return snd_pcm_action(&snd_pcm_action_start, substream, SNDRV_PCM_STATE_RUNNING);
}
+/*
+ * stop callbacks
+ */
static int snd_pcm_pre_stop(snd_pcm_substream_t *substream, int state)
{
snd_pcm_runtime_t *runtime = substream->runtime;
- if (substream->runtime->status->state != SNDRV_PCM_STATE_RUNNING &&
- substream->runtime->status->state != SNDRV_PCM_STATE_DRAINING)
+ if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
return -EBADFD;
runtime->trigger_master = substream;
return 0;
@@ -846,19 +867,22 @@ static int snd_pcm_pre_stop(snd_pcm_substream_t *substream, int state)
static int snd_pcm_do_stop(snd_pcm_substream_t *substream, int state)
{
- if (substream->runtime->trigger_master != substream)
- return 0;
- return substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_STOP);
+ if (substream->runtime->trigger_master == substream &&
+ snd_pcm_running(substream))
+ substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_STOP);
+ return 0; /* unconditonally stop all substreams */
}
static void snd_pcm_post_stop(snd_pcm_substream_t *substream, int state)
{
snd_pcm_runtime_t *runtime = substream->runtime;
- snd_pcm_trigger_tstamp(substream);
- if (substream->timer)
- snd_timer_notify(substream->timer, SNDRV_TIMER_EVENT_MSTOP, &runtime->trigger_tstamp);
- runtime->status->state = state;
- snd_pcm_tick_set(substream, 0);
+ if (runtime->status->state != state) {
+ snd_pcm_trigger_tstamp(substream);
+ if (substream->timer)
+ snd_timer_notify(substream->timer, SNDRV_TIMER_EVENT_MSTOP, &runtime->trigger_tstamp);
+ runtime->status->state = state;
+ snd_pcm_tick_set(substream, 0);
+ }
wake_up(&runtime->sleep);
}
@@ -870,12 +894,30 @@ static struct action_ops snd_pcm_action_stop = {
/**
* snd_pcm_stop
+ *
+ * Try to stop all running streams in the substream group.
+ * The state of each stream is changed to the given value after that unconditionally.
*/
int snd_pcm_stop(snd_pcm_substream_t *substream, int state)
{
return snd_pcm_action(&snd_pcm_action_stop, substream, state);
}
+/**
+ * snd_pcm_drain_done
+ *
+ * Stop the DMA only when the given stream is playback.
+ * The state is changed to SETUP.
+ * Unlike snd_pcm_stop(), this affects only the given stream.
+ */
+int snd_pcm_drain_done(snd_pcm_substream_t *substream)
+{
+ return snd_pcm_action_single(&snd_pcm_action_stop, substream, SNDRV_PCM_STATE_SETUP);
+}
+
+/*
+ * pause callbacks
+ */
static int snd_pcm_pre_pause(snd_pcm_substream_t *substream, int push)
{
snd_pcm_runtime_t *runtime = substream->runtime;
@@ -899,6 +941,14 @@ static int snd_pcm_do_pause(snd_pcm_substream_t *substream, int push)
SNDRV_PCM_TRIGGER_PAUSE_RELEASE);
}
+static void snd_pcm_undo_pause(snd_pcm_substream_t *substream, int push)
+{
+ if (substream->runtime->trigger_master == substream)
+ substream->ops->trigger(substream,
+ push ? SNDRV_PCM_TRIGGER_PAUSE_RELEASE :
+ SNDRV_PCM_TRIGGER_PAUSE_PUSH);
+}
+
static void snd_pcm_post_pause(snd_pcm_substream_t *substream, int push)
{
snd_pcm_runtime_t *runtime = substream->runtime;
@@ -921,9 +971,13 @@ static void snd_pcm_post_pause(snd_pcm_substream_t *substream, int push)
static struct action_ops snd_pcm_action_pause = {
.pre_action = snd_pcm_pre_pause,
.do_action = snd_pcm_do_pause,
+ .undo_action = snd_pcm_undo_pause,
.post_action = snd_pcm_post_pause
};
+/*
+ * Push/release the pause for all linked streams.
+ */
static int snd_pcm_pause(snd_pcm_substream_t *substream, int push)
{
return snd_pcm_action(&snd_pcm_action_pause, substream, push);
@@ -937,7 +991,6 @@ static int snd_pcm_pre_suspend(snd_pcm_substream_t *substream, int state)
snd_pcm_runtime_t *runtime = substream->runtime;
if (runtime->status->state == SNDRV_PCM_STATE_SUSPENDED)
return -EBUSY;
- runtime->status->suspended_state = runtime->status->state;
runtime->trigger_master = substream;
return 0;
}
@@ -947,10 +1000,10 @@ static int snd_pcm_do_suspend(snd_pcm_substream_t *substream, int state)
snd_pcm_runtime_t *runtime = substream->runtime;
if (runtime->trigger_master != substream)
return 0;
- if (runtime->status->suspended_state != SNDRV_PCM_STATE_RUNNING &&
- runtime->status->suspended_state != SNDRV_PCM_STATE_DRAINING)
+ if (! snd_pcm_running(substream))
return 0;
- return substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_SUSPEND);
+ substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_SUSPEND);
+ return 0; /* suspend unconditionally */
}
static void snd_pcm_post_suspend(snd_pcm_substream_t *substream, int state)
@@ -959,6 +1012,7 @@ static void snd_pcm_post_suspend(snd_pcm_substream_t *substream, int state)
snd_pcm_trigger_tstamp(substream);
if (substream->timer)
snd_timer_notify(substream->timer, SNDRV_TIMER_EVENT_MPAUSE, &runtime->trigger_tstamp);
+ runtime->status->suspended_state = runtime->status->state;
runtime->status->state = SNDRV_PCM_STATE_SUSPENDED;
snd_pcm_tick_set(substream, 0);
wake_up(&runtime->sleep);
@@ -972,6 +1026,9 @@ static struct action_ops snd_pcm_action_suspend = {
/**
* snd_pcm_suspend
+ *
+ * Trigger SUSPEND to all linked streams.
+ * After this call, all streams are changed to SUSPENDED state.
*/
int snd_pcm_suspend(snd_pcm_substream_t *substream)
{
@@ -980,11 +1037,14 @@ int snd_pcm_suspend(snd_pcm_substream_t *substream)
/**
* snd_pcm_suspend_all
+ *
+ * Trigger SUSPEND to all substreams in the given pcm.
+ * After this call, all streams are changed to SUSPENDED state.
*/
int snd_pcm_suspend_all(snd_pcm_t *pcm)
{
snd_pcm_substream_t *substream;
- int stream, err;
+ int stream, err = 0;
for (stream = 0; stream < 2; stream++) {
for (substream = pcm->streams[stream].substream; substream; substream = substream->next) {
@@ -992,15 +1052,11 @@ int snd_pcm_suspend_all(snd_pcm_t *pcm)
if (substream->runtime == NULL)
continue;
snd_pcm_stream_lock(substream);
- if (substream->runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) {
- snd_pcm_stream_unlock(substream);
- continue;
- }
- if ((err = snd_pcm_suspend(substream)) < 0) {
- snd_pcm_stream_unlock(substream);
- return err;
- }
+ if (substream->runtime->status->state != SNDRV_PCM_STATE_SUSPENDED)
+ err = snd_pcm_suspend(substream);
snd_pcm_stream_unlock(substream);
+ if (err < 0)
+ return err;
}
}
return 0;
@@ -1022,12 +1078,21 @@ static int snd_pcm_do_resume(snd_pcm_substream_t *substream, int state)
snd_pcm_runtime_t *runtime = substream->runtime;
if (runtime->trigger_master != substream)
return 0;
+ /* DMA not running previously? */
if (runtime->status->suspended_state != SNDRV_PCM_STATE_RUNNING &&
- runtime->status->suspended_state != SNDRV_PCM_STATE_DRAINING)
+ (runtime->status->suspended_state != SNDRV_PCM_STATE_DRAINING ||
+ substream->stream != SNDRV_PCM_STREAM_PLAYBACK))
return 0;
return substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_RESUME);
}
+static void snd_pcm_undo_resume(snd_pcm_substream_t *substream, int state)
+{
+ if (substream->runtime->trigger_master == substream &&
+ snd_pcm_running(substream))
+ substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_SUSPEND);
+}
+
static void snd_pcm_post_resume(snd_pcm_substream_t *substream, int state)
{
snd_pcm_runtime_t *runtime = substream->runtime;
@@ -1042,6 +1107,7 @@ static void snd_pcm_post_resume(snd_pcm_substream_t *substream, int state)
static struct action_ops snd_pcm_action_resume = {
.pre_action = snd_pcm_pre_resume,
.do_action = snd_pcm_do_resume,
+ .undo_action = snd_pcm_undo_resume,
.post_action = snd_pcm_post_resume
};
@@ -1066,6 +1132,11 @@ static int snd_pcm_resume(snd_pcm_substream_t *substream)
#endif /* CONFIG_PM */
+/*
+ * xrun ioctl
+ *
+ * Change the RUNNING stream(s) to XRUN state.
+ */
static int snd_pcm_xrun(snd_pcm_substream_t *substream)
{
snd_card_t *card = substream->pcm->card;
@@ -1073,8 +1144,13 @@ static int snd_pcm_xrun(snd_pcm_substream_t *substream)
int result;
snd_power_lock(card);
+ if (runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) {
+ result = snd_power_wait(card, SNDRV_CTL_POWER_D0, substream->ffile);
+ if (result < 0)
+ goto _unlock;
+ }
+
snd_pcm_stream_lock_irq(substream);
- _xrun_recovery:
switch (runtime->status->state) {
case SNDRV_PCM_STATE_XRUN:
result = 0; /* already there */
@@ -1082,21 +1158,18 @@ static int snd_pcm_xrun(snd_pcm_substream_t *substream)
case SNDRV_PCM_STATE_RUNNING:
result = snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
break;
- case SNDRV_PCM_STATE_SUSPENDED:
- snd_pcm_stream_unlock_irq(substream);
- result = snd_power_wait(card, SNDRV_CTL_POWER_D0, substream->ffile);
- snd_pcm_stream_lock_irq(substream);
- if (result >= 0)
- goto _xrun_recovery;
- break;
default:
result = -EBADFD;
}
snd_pcm_stream_unlock_irq(substream);
+ _unlock:
snd_power_unlock(card);
return result;
}
+/*
+ * reset ioctl
+ */
static int snd_pcm_pre_reset(snd_pcm_substream_t * substream, int state)
{
snd_pcm_runtime_t *runtime = substream->runtime;
@@ -1145,17 +1218,17 @@ static int snd_pcm_reset(snd_pcm_substream_t *substream)
return snd_pcm_action_nonatomic(&snd_pcm_action_reset, substream, 0);
}
+/*
+ * prepare ioctl
+ */
static int snd_pcm_pre_prepare(snd_pcm_substream_t * substream, int state)
{
snd_pcm_runtime_t *runtime = substream->runtime;
- switch (runtime->status->state) {
- case SNDRV_PCM_STATE_OPEN:
+ if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
return -EBADFD;
- case SNDRV_PCM_STATE_RUNNING:
+ if (snd_pcm_running(substream))
return -EBUSY;
- default:
- return 0;
- }
+ return 0;
}
static int snd_pcm_do_prepare(snd_pcm_substream_t * substream, int state)
@@ -1195,111 +1268,147 @@ int snd_pcm_prepare(snd_pcm_substream_t *substream)
return res;
}
-static void snd_pcm_change_state(snd_pcm_substream_t *substream, int state)
+/*
+ * drain ioctl
+ */
+
+static int snd_pcm_pre_drain_init(snd_pcm_substream_t * substream, int state)
{
- struct list_head *pos;
- snd_pcm_substream_t *s;
+ if (substream->ffile->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+ substream->runtime->trigger_master = substream;
+ return 0;
+}
- if (snd_pcm_stream_linked(substream)) {
- if (!spin_trylock(&substream->group->lock)) {
- spin_unlock(&substream->self_group.lock);
- spin_lock(&substream->group->lock);
- spin_lock(&substream->self_group.lock);
- }
- snd_pcm_group_for_each(pos, substream) {
- s = snd_pcm_group_substream_entry(pos);
- if (s != substream)
- spin_lock(&s->self_group.lock);
- s->runtime->status->state = state;
- if (s != substream)
- spin_unlock(&s->self_group.lock);
+static int snd_pcm_do_drain_init(snd_pcm_substream_t * substream, int state)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ switch (runtime->status->state) {
+ case SNDRV_PCM_STATE_PREPARED:
+ /* start playback stream if possible */
+ if (! snd_pcm_playback_empty(substream)) {
+ snd_pcm_do_start(substream, SNDRV_PCM_STATE_DRAINING);
+ snd_pcm_post_start(substream, SNDRV_PCM_STATE_DRAINING);
+ }
+ break;
+ case SNDRV_PCM_STATE_RUNNING:
+ runtime->status->state = SNDRV_PCM_STATE_DRAINING;
+ break;
+ default:
+ break;
}
- spin_unlock(&substream->group->lock);
} else {
- substream->runtime->status->state = state;
+ /* stop running stream */
+ if (runtime->status->state == SNDRV_PCM_STATE_RUNNING) {
+ int state = snd_pcm_capture_avail(runtime) > 0 ?
+ SNDRV_PCM_STATE_DRAINING : SNDRV_PCM_STATE_SETUP;
+ snd_pcm_do_stop(substream, state);
+ snd_pcm_post_stop(substream, state);
+ }
}
+ return 0;
}
-static int snd_pcm_playback_drop(snd_pcm_substream_t *substream);
+static void snd_pcm_post_drain_init(snd_pcm_substream_t * substream, int state)
+{
+}
+
+static struct action_ops snd_pcm_action_drain_init = {
+ .pre_action = snd_pcm_pre_drain_init,
+ .do_action = snd_pcm_do_drain_init,
+ .post_action = snd_pcm_post_drain_init
+};
+
+struct drain_rec {
+ snd_pcm_substream_t *substream;
+ wait_queue_t wait;
+ snd_pcm_uframes_t stop_threshold;
+};
+
+static int snd_pcm_drop(snd_pcm_substream_t *substream);
-static int snd_pcm_playback_drain(snd_pcm_substream_t * substream)
+/*
+ * Drain the stream(s).
+ * When the substream is linked, sync until the draining of all playback streams
+ * is finished.
+ * After this call, all streams are supposed to be either SETUP or DRAINING
+ * (capture only) state.
+ */
+static int snd_pcm_drain(snd_pcm_substream_t *substream)
{
snd_card_t *card;
snd_pcm_runtime_t *runtime;
- int err, result = 0;
- wait_queue_t wait;
- enum { READY, EXPIRED, SUSPENDED, SIGNALED } state = READY;
- snd_pcm_uframes_t stop_threshold;
+ struct list_head *pos;
+ int result = 0;
+ int i, num_drecs;
+ struct drain_rec *drec, drec_tmp, *d;
snd_assert(substream != NULL, return -ENXIO);
- snd_assert(substream->stream == SNDRV_PCM_STREAM_PLAYBACK, return -EINVAL);
- runtime = substream->runtime;
card = substream->pcm->card;
+ runtime = substream->runtime;
+ if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
+ return -EBADFD;
+
+ down_read(&snd_pcm_link_rwsem);
snd_power_lock(card);
- snd_pcm_stream_lock_irq(substream);
+ if (runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) {
+ result = snd_power_wait(card, SNDRV_CTL_POWER_D0, substream->ffile);
+ if (result < 0)
+ goto _unlock;
+ }
- /* stop_threshold fixup to avoid endless loop when */
- /* stop_threshold > buffer_size */
- stop_threshold = runtime->stop_threshold;
- if (runtime->stop_threshold > runtime->buffer_size)
- runtime->stop_threshold = runtime->buffer_size;
+ /* allocate temporary record for drain sync */
+ if (snd_pcm_stream_linked(substream)) {
+ drec = kmalloc(substream->group->count * sizeof(*drec), GFP_KERNEL);
+ if (! drec) {
+ result = -ENOMEM;
+ goto _unlock;
+ }
+ } else
+ drec = &drec_tmp;
- switch (runtime->status->state) {
- case SNDRV_PCM_STATE_PAUSED:
+ snd_pcm_stream_lock_irq(substream);
+ /* resume pause */
+ if (runtime->status->state == SNDRV_PCM_STATE_PAUSED)
snd_pcm_pause(substream, 0);
- /* Fall through */
- case SNDRV_PCM_STATE_RUNNING:
- case SNDRV_PCM_STATE_DRAINING:
- break;
- case SNDRV_PCM_STATE_SUSPENDED:
- snd_pcm_stream_unlock_irq(substream);
- result = snd_power_wait(card, SNDRV_CTL_POWER_D0, substream->ffile);
- snd_pcm_stream_lock_irq(substream);
- if (result >= 0)
- snd_pcm_change_state(substream, SNDRV_PCM_STATE_SETUP);
- goto _end;
- case SNDRV_PCM_STATE_OPEN:
- result = -EBADFD;
- goto _end;
- case SNDRV_PCM_STATE_PREPARED:
- if (!snd_pcm_playback_empty(substream)) {
- err = snd_pcm_start(substream);
- if (err < 0) {
- result = err;
- goto _end;
- }
- break;
- }
- /* Fall through */
- case SNDRV_PCM_STATE_XRUN:
- snd_pcm_change_state(substream, SNDRV_PCM_STATE_SETUP);
- /* Fall through */
- case SNDRV_PCM_STATE_SETUP:
+
+ /* 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;
- default:
- break;
- }
- if (runtime->status->state == SNDRV_PCM_STATE_RUNNING) {
- if (snd_pcm_playback_empty(substream)) {
- snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP);
- goto _end;
+ /* check streams with PLAYBACK & DRAINING */
+ num_drecs = 0;
+ snd_pcm_group_for_each(pos, substream) {
+ snd_pcm_substream_t *s = snd_pcm_group_substream_entry(pos);
+ 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) {
+ d = &drec[num_drecs++];
+ d->substream = s;
+ init_waitqueue_entry(&d->wait, current);
+ add_wait_queue(&runtime->sleep, &d->wait);
+ /* stop_threshold fixup to avoid endless loop when
+ * stop_threshold > buffer_size
+ */
+ d->stop_threshold = runtime->stop_threshold;
+ if (runtime->stop_threshold > runtime->buffer_size)
+ runtime->stop_threshold = runtime->buffer_size;
}
- snd_pcm_change_state(substream, SNDRV_PCM_STATE_DRAINING);
}
- if (substream->ffile->f_flags & O_NONBLOCK) {
- result = -EAGAIN;
+ if (! num_drecs)
goto _end;
- }
- init_waitqueue_entry(&wait, current);
- add_wait_queue(&runtime->sleep, &wait);
- while (1) {
+ for (;;) {
long tout;
if (signal_pending(current)) {
- state = SIGNALED;
+ result = -ERESTARTSYS;
break;
}
set_current_state(TASK_INTERRUPTIBLE);
@@ -1309,177 +1418,81 @@ static int snd_pcm_playback_drain(snd_pcm_substream_t * substream)
snd_power_lock(card);
snd_pcm_stream_lock_irq(substream);
if (tout == 0) {
- state = runtime->status->state == SNDRV_PCM_STATE_SUSPENDED ? SUSPENDED : EXPIRED;
+ if (substream->runtime->status->state == SNDRV_PCM_STATE_SUSPENDED)
+ result = -ESTRPIPE;
+ else {
+ snd_printd("playback drain error (DMA or IRQ trouble?)\n");
+ snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP);
+ result = -EIO;
+ }
break;
}
- if (runtime->status->state != SNDRV_PCM_STATE_DRAINING) {
- state = READY;
- 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;
}
- remove_wait_queue(&runtime->sleep, &wait);
-
- switch (state) {
- case SIGNALED:
- result = -ERESTARTSYS;
- goto _end;
- case SUSPENDED:
- result = -ESTRPIPE;
- goto _end;
- case EXPIRED:
- snd_printd("playback drain error (DMA or IRQ trouble?)\n");
- result = -EIO;
- goto _end;
- default:
- break;
+ for (i = 0; i < num_drecs; i++) {
+ d = &drec[i];
+ runtime = d->substream->runtime;
+ remove_wait_queue(&runtime->sleep, &d->wait);
+ runtime->stop_threshold = d->stop_threshold;
}
- _end:
- runtime->stop_threshold = stop_threshold;
+ _end:
snd_pcm_stream_unlock_irq(substream);
+ if (drec && drec != &drec_tmp)
+ kfree(drec);
+ _unlock:
snd_power_unlock(card);
- if (state == EXPIRED)
- snd_pcm_playback_drop(substream);
+ up_read(&snd_pcm_link_rwsem);
return result;
}
-static int snd_pcm_playback_drop(snd_pcm_substream_t *substream)
+/*
+ * drop ioctl
+ *
+ * Immediately put all linked substreams into SETUP state.
+ */
+static int snd_pcm_drop(snd_pcm_substream_t *substream)
{
- snd_pcm_runtime_t *runtime = substream->runtime;
- snd_card_t *card = substream->pcm->card;
- int res = 0;
+ snd_pcm_runtime_t *runtime;
+ snd_card_t *card;
+ int result = 0;
- snd_power_lock(card);
- snd_pcm_stream_lock_irq(substream);
- switch (runtime->status->state) {
- case SNDRV_PCM_STATE_OPEN:
- res = -EBADFD;
- break;
- case SNDRV_PCM_STATE_SETUP:
- break;
- case SNDRV_PCM_STATE_PAUSED:
- snd_pcm_pause(substream, 0);
- /* Fall through */
- case SNDRV_PCM_STATE_RUNNING:
- case SNDRV_PCM_STATE_DRAINING:
- if (snd_pcm_update_hw_ptr(substream) >= 0) {
- snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP);
- break;
- }
- /* Fall through */
- case SNDRV_PCM_STATE_PREPARED:
- case SNDRV_PCM_STATE_XRUN:
- snd_pcm_change_state(substream, SNDRV_PCM_STATE_SETUP);
- break;
- case SNDRV_PCM_STATE_SUSPENDED:
- snd_pcm_stream_unlock_irq(substream);
- res = snd_power_wait(card, SNDRV_CTL_POWER_D0, substream->ffile);
- snd_pcm_stream_lock_irq(substream);
- if (res >= 0)
- snd_pcm_change_state(substream, SNDRV_PCM_STATE_SETUP);
- break;
- default:
- break;
- }
- runtime->control->appl_ptr = runtime->status->hw_ptr;
- snd_pcm_stream_unlock_irq(substream);
- snd_power_unlock(card);
- return res;
-}
+ snd_assert(substream != NULL, return -ENXIO);
+ runtime = substream->runtime;
+ card = substream->pcm->card;
-static int snd_pcm_capture_drain(snd_pcm_substream_t * substream)
-{
- snd_pcm_runtime_t *runtime = substream->runtime;
- snd_card_t *card = substream->pcm->card;
- int res = 0;
+ if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
+ return -EBADFD;
snd_power_lock(card);
- snd_pcm_stream_lock_irq(substream);
- switch (runtime->status->state) {
- case SNDRV_PCM_STATE_OPEN:
- res = -EBADFD;
- break;
- case SNDRV_PCM_STATE_PREPARED:
- snd_pcm_change_state(substream, SNDRV_PCM_STATE_SETUP);
- break;
- case SNDRV_PCM_STATE_SETUP:
- case SNDRV_PCM_STATE_DRAINING:
- break;
- case SNDRV_PCM_STATE_PAUSED:
- snd_pcm_pause(substream, 0);
- /* Fall through */
- case SNDRV_PCM_STATE_RUNNING:
- if (snd_pcm_update_hw_ptr(substream) >= 0) {
- snd_pcm_stop(substream,
- snd_pcm_capture_avail(runtime) > 0 ?
- SNDRV_PCM_STATE_DRAINING : SNDRV_PCM_STATE_SETUP);
- break;
- }
- /* Fall through */
- case SNDRV_PCM_STATE_XRUN:
- _xrun_recovery:
- snd_pcm_change_state(substream,
- snd_pcm_capture_avail(runtime) > 0 ?
- SNDRV_PCM_STATE_DRAINING : SNDRV_PCM_STATE_SETUP);
- break;
- case SNDRV_PCM_STATE_SUSPENDED:
- snd_pcm_stream_unlock_irq(substream);
- res = snd_power_wait(card, SNDRV_CTL_POWER_D0, substream->ffile);
- snd_pcm_stream_lock_irq(substream);
- if (res >= 0)
- goto _xrun_recovery;
- break;
- default:
- break;
+ if (runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) {
+ result = snd_power_wait(card, SNDRV_CTL_POWER_D0, substream->ffile);
+ if (result < 0)
+ goto _unlock;
}
- snd_pcm_stream_unlock_irq(substream);
- snd_power_unlock(card);
- return res;
-}
-
-static int snd_pcm_capture_drop(snd_pcm_substream_t * substream)
-{
- snd_pcm_runtime_t *runtime = substream->runtime;
- snd_card_t *card = substream->pcm->card;
- int res = 0;
- snd_power_lock(card);
snd_pcm_stream_lock_irq(substream);
- switch (runtime->status->state) {
- case SNDRV_PCM_STATE_OPEN:
- res = -EBADFD;
- break;
- case SNDRV_PCM_STATE_SETUP:
- break;
- case SNDRV_PCM_STATE_PAUSED:
+ /* resume pause */
+ if (runtime->status->state == SNDRV_PCM_STATE_PAUSED)
snd_pcm_pause(substream, 0);
- /* Fall through */
- case SNDRV_PCM_STATE_RUNNING:
- snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP);
- break;
- case SNDRV_PCM_STATE_SUSPENDED:
- snd_pcm_stream_unlock_irq(substream);
- res = snd_power_wait(card, SNDRV_CTL_POWER_D0, substream->ffile);
- snd_pcm_stream_lock_irq(substream);
- if (res < 0)
- goto _end;
- /* Fall through */
- case SNDRV_PCM_STATE_PREPARED:
- case SNDRV_PCM_STATE_DRAINING:
- case SNDRV_PCM_STATE_XRUN:
- snd_pcm_change_state(substream, SNDRV_PCM_STATE_SETUP);
- break;
- default:
- break;
- }
- runtime->control->appl_ptr = runtime->status->hw_ptr;
- _end:
+
+ snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP);
+ /* runtime->control->appl_ptr = runtime->status->hw_ptr; */
snd_pcm_stream_unlock_irq(substream);
+ _unlock:
snd_power_unlock(card);
- return res;
+ return result;
}
+
/* WARNING: Don't forget to fput back the file */
extern int snd_major;
static struct file *snd_pcm_file_fd(int fd)
@@ -1505,6 +1518,9 @@ static struct file *snd_pcm_file_fd(int fd)
return file;
}
+/*
+ * PCM link handling
+ */
static int snd_pcm_link(snd_pcm_substream_t *substream, int fd)
{
int res = 0;
@@ -1512,12 +1528,6 @@ static int snd_pcm_link(snd_pcm_substream_t *substream, int fd)
snd_pcm_file_t *pcm_file;
snd_pcm_substream_t *substream1;
- snd_pcm_stream_lock_irq(substream);
- if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
- snd_pcm_stream_unlock_irq(substream);
- return -EBADFD;
- }
- snd_pcm_stream_unlock_irq(substream);
file = snd_pcm_file_fd(fd);
if (!file)
return -EBADFD;
@@ -1525,7 +1535,8 @@ static int snd_pcm_link(snd_pcm_substream_t *substream, int fd)
substream1 = pcm_file->substream;
down_write(&snd_pcm_link_rwsem);
write_lock_irq(&snd_pcm_link_rwlock);
- if (substream->runtime->status->state != substream1->runtime->status->state) {
+ if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN ||
+ substream->runtime->status->state != substream1->runtime->status->state) {
res = -EBADFD;
goto _end;
}
@@ -1542,8 +1553,10 @@ static int snd_pcm_link(snd_pcm_substream_t *substream, int fd)
spin_lock_init(&substream->group->lock);
INIT_LIST_HEAD(&substream->group->substreams);
list_add_tail(&substream->link_list, &substream->group->substreams);
+ substream->group->count = 1;
}
list_add_tail(&substream1->link_list, &substream->group->substreams);
+ substream->group->count++;
substream1->group = substream->group;
_end:
write_unlock_irq(&snd_pcm_link_rwlock);
@@ -1562,7 +1575,7 @@ static void relink_to_local(snd_pcm_substream_t *substream)
static int snd_pcm_unlink(snd_pcm_substream_t *substream)
{
struct list_head *pos;
- int res = 0, count = 0;
+ int res = 0;
down_write(&snd_pcm_link_rwsem);
write_lock_irq(&snd_pcm_link_rwlock);
@@ -1571,11 +1584,8 @@ static int snd_pcm_unlink(snd_pcm_substream_t *substream)
goto _end;
}
list_del(&substream->link_list);
- snd_pcm_group_for_each(pos, substream) {
- if (++count > 1)
- break;
- }
- if (count == 1) { /* detach the last stream, too */
+ substream->group->count--;
+ if (substream->group->count == 1) { /* detach the last stream, too */
snd_pcm_group_for_each(pos, substream) {
relink_to_local(snd_pcm_group_substream_entry(pos));
break;
@@ -1589,6 +1599,9 @@ static int snd_pcm_unlink(snd_pcm_substream_t *substream)
return res;
}
+/*
+ * hw configurator
+ */
static int snd_pcm_hw_rule_mul(snd_pcm_hw_params_t *params,
snd_pcm_hw_rule_t *rule)
{
@@ -2077,10 +2090,7 @@ int snd_pcm_release(struct inode *inode, struct file *file)
snd_assert(substream != NULL, return -ENXIO);
snd_assert(!atomic_read(&substream->runtime->mmap_count), );
pcm = substream->pcm;
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
- snd_pcm_playback_drop(substream);
- else
- snd_pcm_capture_drop(substream);
+ snd_pcm_drop(substream);
fasync_helper(-1, file, 0, &substream->runtime->fasync);
down(&pcm->open_mutex);
snd_pcm_release_file(pcm_file);
@@ -2436,7 +2446,7 @@ static int snd_pcm_common_ioctl1(snd_pcm_substream_t *substream,
case SNDRV_PCM_IOCTL_RESET:
return snd_pcm_reset(substream);
case SNDRV_PCM_IOCTL_START:
- return snd_pcm_action_lock_irq(&snd_pcm_action_start, substream, 0);
+ return snd_pcm_action_lock_irq(&snd_pcm_action_start, substream, SNDRV_PCM_STATE_RUNNING);
case SNDRV_PCM_IOCTL_LINK:
return snd_pcm_link(substream, (int)(unsigned long) arg);
case SNDRV_PCM_IOCTL_UNLINK:
@@ -2455,6 +2465,10 @@ static int snd_pcm_common_ioctl1(snd_pcm_substream_t *substream,
return snd_pcm_hw_refine_old_user(substream, arg);
case SNDRV_PCM_IOCTL_HW_PARAMS_OLD:
return snd_pcm_hw_params_old_user(substream, arg);
+ case SNDRV_PCM_IOCTL_DRAIN:
+ return snd_pcm_drain(substream);
+ case SNDRV_PCM_IOCTL_DROP:
+ return snd_pcm_drop(substream);
}
snd_printd("unknown ioctl = 0x%x\n", cmd);
return -ENOTTY;
@@ -2543,10 +2557,6 @@ static int snd_pcm_playback_ioctl1(snd_pcm_substream_t *substream,
snd_pcm_stream_unlock_irq(substream);
return res;
}
- case SNDRV_PCM_IOCTL_DRAIN:
- return snd_pcm_playback_drain(substream);
- case SNDRV_PCM_IOCTL_DROP:
- return snd_pcm_playback_drop(substream);
}
return snd_pcm_common_ioctl1(substream, cmd, arg);
}
@@ -2626,10 +2636,6 @@ static int snd_pcm_capture_ioctl1(snd_pcm_substream_t *substream,
__put_user(result, _frames);
return result < 0 ? result : 0;
}
- case SNDRV_PCM_IOCTL_DRAIN:
- return snd_pcm_capture_drain(substream);
- case SNDRV_PCM_IOCTL_DROP:
- return snd_pcm_capture_drop(substream);
}
return snd_pcm_common_ioctl1(substream, cmd, arg);
}