ALSA: pcm - Fix drain behavior in non-blocking mode
[pandora-kernel.git] / sound / core / pcm_native.c
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: