ALSA: pcm - Fix drain behavior in non-blocking mode
authorTakashi Iwai <tiwai@suse.de>
Thu, 20 Aug 2009 14:40:16 +0000 (16:40 +0200)
committerTakashi Iwai <tiwai@suse.de>
Thu, 20 Aug 2009 14:40:16 +0000 (16:40 +0200)
The current PCM core has the following problems regarding PCM draining
in non-blocking mode:

- the current f_flags isn't checked in snd_pcm_drain(), thus changing
  the mode dynamically via snd_pcm_nonblock() after open doesn't work.
- calling drain in non-blocking mode just return -EAGAIN error, but
  doesn't provide any way to sync with draining.

This patch fixes these issues.
- check file->f_flags in snd_pcm_drain() properly
- when O_NONBLOCK is set, PCM core sets the stream(s) to DRAIN state
  but quits ioctl immediately without waiting the whole drain; the
  caller can sync the drain manually via poll()

Signed-off-by: Takashi Iwai <tiwai@suse.de>
sound/core/pcm_lib.c
sound/core/pcm_native.c

index 72cfd47..e3e78c7 100644 (file)
@@ -197,12 +197,16 @@ static int snd_pcm_update_hw_ptr_post(struct snd_pcm_substream *substream,
                avail = snd_pcm_capture_avail(runtime);
        if (avail > runtime->avail_max)
                runtime->avail_max = avail;
-       if (avail >= runtime->stop_threshold) {
-               if (substream->runtime->status->state == SNDRV_PCM_STATE_DRAINING)
+       if (runtime->status->state == SNDRV_PCM_STATE_DRAINING) {
+               if (avail >= runtime->buffer_size) {
                        snd_pcm_drain_done(substream);
-               else
+                       return -EPIPE;
+               }
+       } else {
+               if (avail >= runtime->stop_threshold) {
                        xrun(substream);
-               return -EPIPE;
+                       return -EPIPE;
+               }
        }
        if (avail >= runtime->control->avail_min)
                wake_up(&runtime->sleep);
index ac2150e..b08898c 100644 (file)
@@ -1343,8 +1343,6 @@ static int snd_pcm_prepare(struct snd_pcm_substream *substream,
 
 static int snd_pcm_pre_drain_init(struct snd_pcm_substream *substream, int state)
 {
-       if (substream->f_flags & O_NONBLOCK)
-               return -EAGAIN;
        substream->runtime->trigger_master = substream;
        return 0;
 }
@@ -1392,7 +1390,6 @@ static struct action_ops snd_pcm_action_drain_init = {
 struct drain_rec {
        struct snd_pcm_substream *substream;
        wait_queue_t wait;
-       snd_pcm_uframes_t stop_threshold;
 };
 
 static int snd_pcm_drop(struct snd_pcm_substream *substream);
@@ -1404,13 +1401,15 @@ static int snd_pcm_drop(struct snd_pcm_substream *substream);
  * After this call, all streams are supposed to be either SETUP or DRAINING
  * (capture only) state.
  */
-static int snd_pcm_drain(struct snd_pcm_substream *substream)
+static int snd_pcm_drain(struct snd_pcm_substream *substream,
+                        struct file *file)
 {
        struct snd_card *card;
        struct snd_pcm_runtime *runtime;
        struct snd_pcm_substream *s;
        int result = 0;
        int i, num_drecs;
+       int nonblock = 0;
        struct drain_rec *drec, drec_tmp, *d;
 
        card = substream->pcm->card;
@@ -1428,6 +1427,15 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream)
                }
        }
 
+       if (file) {
+               if (file->f_flags & O_NONBLOCK)
+                       nonblock = 1;
+       } else if (substream->f_flags & O_NONBLOCK)
+               nonblock = 1;
+
+       if (nonblock)
+               goto lock; /* no need to allocate waitqueues */
+
        /* allocate temporary record for drain sync */
        down_read(&snd_pcm_link_rwsem);
        if (snd_pcm_stream_linked(substream)) {
@@ -1449,16 +1457,11 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream)
                        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;
                }
        }
        up_read(&snd_pcm_link_rwsem);
 
+ lock:
        snd_pcm_stream_lock_irq(substream);
        /* resume pause */
        if (substream->runtime->status->state == SNDRV_PCM_STATE_PAUSED)
@@ -1466,9 +1469,12 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream)
 
        /* 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;
+       if (result < 0)
+               goto unlock;
+       /* in non-blocking, we don't wait in ioctl but let caller poll */
+       if (nonblock) {
+               result = -EAGAIN;
+               goto unlock;
        }
 
        for (;;) {
@@ -1504,18 +1510,18 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream)
                }
        }
 
+ unlock:
        snd_pcm_stream_unlock_irq(substream);
 
- _error:
-       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;
+       if (!nonblock) {
+               for (i = 0; i < num_drecs; i++) {
+                       d = &drec[i];
+                       runtime = d->substream->runtime;
+                       remove_wait_queue(&runtime->sleep, &d->wait);
+               }
+               if (drec != &drec_tmp)
+                       kfree(drec);
        }
-
-       if (drec != &drec_tmp)
-               kfree(drec);
        snd_power_unlock(card);
 
        return result;
@@ -2544,7 +2550,7 @@ static int snd_pcm_common_ioctl1(struct file *file,
                return snd_pcm_hw_params_old_user(substream, arg);
 #endif
        case SNDRV_PCM_IOCTL_DRAIN:
-               return snd_pcm_drain(substream);
+               return snd_pcm_drain(substream, file);
        case SNDRV_PCM_IOCTL_DROP:
                return snd_pcm_drop(substream);
        case SNDRV_PCM_IOCTL_PAUSE: