ALSA: aloop: Sync stale timer before release
[pandora-kernel.git] / sound / drivers / aloop.c
index 4067f15..3fa12d9 100644 (file)
 #include <linux/slab.h>
 #include <linux/time.h>
 #include <linux/wait.h>
-#include <linux/moduleparam.h>
+#include <linux/module.h>
 #include <linux/platform_device.h>
 #include <sound/core.h>
 #include <sound/control.h>
 #include <sound/pcm.h>
+#include <sound/pcm_params.h>
 #include <sound/info.h>
 #include <sound/initval.h>
 
@@ -119,6 +120,7 @@ struct loopback_pcm {
        unsigned int period_size_frac;
        unsigned long last_jiffies;
        struct timer_list timer;
+       spinlock_t timer_lock;
 };
 
 static struct platform_device *devices[SNDRV_CARDS];
@@ -169,6 +171,7 @@ static void loopback_timer_start(struct loopback_pcm *dpcm)
        unsigned long tick;
        unsigned int rate_shift = get_rate_shift(dpcm);
 
+       spin_lock(&dpcm->timer_lock);
        if (rate_shift != dpcm->pcm_rate_shift) {
                dpcm->pcm_rate_shift = rate_shift;
                dpcm->period_size_frac = frac_pos(dpcm, dpcm->pcm_period_size);
@@ -181,12 +184,20 @@ static void loopback_timer_start(struct loopback_pcm *dpcm)
        tick = (tick + dpcm->pcm_bps - 1) / dpcm->pcm_bps;
        dpcm->timer.expires = jiffies + tick;
        add_timer(&dpcm->timer);
+       spin_unlock(&dpcm->timer_lock);
 }
 
 static inline void loopback_timer_stop(struct loopback_pcm *dpcm)
 {
+       spin_lock(&dpcm->timer_lock);
        del_timer(&dpcm->timer);
        dpcm->timer.expires = 0;
+       spin_unlock(&dpcm->timer_lock);
+}
+
+static inline void loopback_timer_stop_sync(struct loopback_pcm *dpcm)
+{
+       del_timer_sync(&dpcm->timer);
 }
 
 #define CABLE_VALID_PLAYBACK   (1 << SNDRV_PCM_STREAM_PLAYBACK)
@@ -282,12 +293,14 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
                        loopback_active_notify(dpcm);
                break;
        case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+       case SNDRV_PCM_TRIGGER_SUSPEND:
                spin_lock(&cable->lock);        
                cable->pause |= stream;
                spin_unlock(&cable->lock);
                loopback_timer_stop(dpcm);
                break;
        case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+       case SNDRV_PCM_TRIGGER_RESUME:
                spin_lock(&cable->lock);
                dpcm->last_jiffies = jiffies;
                cable->pause &= ~stream;
@@ -300,19 +313,6 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
        return 0;
 }
 
-static void params_change_substream(struct loopback_pcm *dpcm,
-                                   struct snd_pcm_runtime *runtime)
-{
-       struct snd_pcm_runtime *dst_runtime;
-
-       if (dpcm == NULL || dpcm->substream == NULL)
-               return;
-       dst_runtime = dpcm->substream->runtime;
-       if (dst_runtime == NULL)
-               return;
-       dst_runtime->hw = dpcm->cable->hw;
-}
-
 static void params_change(struct snd_pcm_substream *substream)
 {
        struct snd_pcm_runtime *runtime = substream->runtime;
@@ -324,10 +324,6 @@ static void params_change(struct snd_pcm_substream *substream)
        cable->hw.rate_max = runtime->rate;
        cable->hw.channels_min = runtime->channels;
        cable->hw.channels_max = runtime->channels;
-       params_change_substream(cable->streams[SNDRV_PCM_STREAM_PLAYBACK],
-                               runtime);
-       params_change_substream(cable->streams[SNDRV_PCM_STREAM_CAPTURE],
-                               runtime);
 }
 
 static int loopback_prepare(struct snd_pcm_substream *substream)
@@ -337,6 +333,8 @@ static int loopback_prepare(struct snd_pcm_substream *substream)
        struct loopback_cable *cable = dpcm->cable;
        int bps, salign;
 
+       loopback_timer_stop_sync(dpcm);
+
        salign = (snd_pcm_format_width(runtime->format) *
                                                runtime->channels) / 8;
        bps = salign * runtime->rate;
@@ -547,7 +545,8 @@ static snd_pcm_uframes_t loopback_pointer(struct snd_pcm_substream *substream)
 static struct snd_pcm_hardware loopback_pcm_hardware =
 {
        .info =         (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP |
-                        SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_PAUSE),
+                        SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_PAUSE |
+                        SNDRV_PCM_INFO_RESUME),
        .formats =      (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE |
                         SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S32_BE |
                         SNDRV_PCM_FMTBIT_FLOAT_LE | SNDRV_PCM_FMTBIT_FLOAT_BE),
@@ -602,26 +601,29 @@ static unsigned int get_cable_index(struct snd_pcm_substream *substream)
 static int rule_format(struct snd_pcm_hw_params *params,
                       struct snd_pcm_hw_rule *rule)
 {
+       struct loopback_pcm *dpcm = rule->private;
+       struct loopback_cable *cable = dpcm->cable;
+       struct snd_mask m;
 
-       struct snd_pcm_hardware *hw = rule->private;
-       struct snd_mask *maskp = hw_param_mask(params, rule->var);
-
-       maskp->bits[0] &= (u_int32_t)hw->formats;
-       maskp->bits[1] &= (u_int32_t)(hw->formats >> 32);
-       memset(maskp->bits + 2, 0, (SNDRV_MASK_MAX-64) / 8); /* clear rest */
-       if (! maskp->bits[0] && ! maskp->bits[1])
-               return -EINVAL;
-       return 0;
+       snd_mask_none(&m);
+       mutex_lock(&dpcm->loopback->cable_lock);
+       m.bits[0] = (u_int32_t)cable->hw.formats;
+       m.bits[1] = (u_int32_t)(cable->hw.formats >> 32);
+       mutex_unlock(&dpcm->loopback->cable_lock);
+       return snd_mask_refine(hw_param_mask(params, rule->var), &m);
 }
 
 static int rule_rate(struct snd_pcm_hw_params *params,
                     struct snd_pcm_hw_rule *rule)
 {
-       struct snd_pcm_hardware *hw = rule->private;
+       struct loopback_pcm *dpcm = rule->private;
+       struct loopback_cable *cable = dpcm->cable;
        struct snd_interval t;
 
-        t.min = hw->rate_min;
-        t.max = hw->rate_max;
+       mutex_lock(&dpcm->loopback->cable_lock);
+       t.min = cable->hw.rate_min;
+       t.max = cable->hw.rate_max;
+       mutex_unlock(&dpcm->loopback->cable_lock);
         t.openmin = t.openmax = 0;
         t.integer = 0;
        return snd_interval_refine(hw_param_interval(params, rule->var), &t);
@@ -630,22 +632,44 @@ static int rule_rate(struct snd_pcm_hw_params *params,
 static int rule_channels(struct snd_pcm_hw_params *params,
                         struct snd_pcm_hw_rule *rule)
 {
-       struct snd_pcm_hardware *hw = rule->private;
+       struct loopback_pcm *dpcm = rule->private;
+       struct loopback_cable *cable = dpcm->cable;
        struct snd_interval t;
 
-        t.min = hw->channels_min;
-        t.max = hw->channels_max;
+       mutex_lock(&dpcm->loopback->cable_lock);
+       t.min = cable->hw.channels_min;
+       t.max = cable->hw.channels_max;
+       mutex_unlock(&dpcm->loopback->cable_lock);
         t.openmin = t.openmax = 0;
         t.integer = 0;
        return snd_interval_refine(hw_param_interval(params, rule->var), &t);
 }
 
+static void free_cable(struct snd_pcm_substream *substream)
+{
+       struct loopback *loopback = substream->private_data;
+       int dev = get_cable_index(substream);
+       struct loopback_cable *cable;
+
+       cable = loopback->cables[substream->number][dev];
+       if (!cable)
+               return;
+       if (cable->streams[!substream->stream]) {
+               /* other stream is still alive */
+               cable->streams[substream->stream] = NULL;
+       } else {
+               /* free the cable */
+               loopback->cables[substream->number][dev] = NULL;
+               kfree(cable);
+       }
+}
+
 static int loopback_open(struct snd_pcm_substream *substream)
 {
        struct snd_pcm_runtime *runtime = substream->runtime;
        struct loopback *loopback = substream->private_data;
        struct loopback_pcm *dpcm;
-       struct loopback_cable *cable;
+       struct loopback_cable *cable = NULL;
        int err = 0;
        int dev = get_cable_index(substream);
 
@@ -659,12 +683,12 @@ static int loopback_open(struct snd_pcm_substream *substream)
        dpcm->substream = substream;
        setup_timer(&dpcm->timer, loopback_timer_function,
                    (unsigned long)dpcm);
+       spin_lock_init(&dpcm->timer_lock);
 
        cable = loopback->cables[substream->number][dev];
        if (!cable) {
                cable = kzalloc(sizeof(*cable), GFP_KERNEL);
                if (!cable) {
-                       kfree(dpcm);
                        err = -ENOMEM;
                        goto unlock;
                }
@@ -682,19 +706,19 @@ static int loopback_open(struct snd_pcm_substream *substream)
        /* are cached -> they do not reflect the actual state */
        err = snd_pcm_hw_rule_add(runtime, 0,
                                  SNDRV_PCM_HW_PARAM_FORMAT,
-                                 rule_format, &runtime->hw,
+                                 rule_format, dpcm,
                                  SNDRV_PCM_HW_PARAM_FORMAT, -1);
        if (err < 0)
                goto unlock;
        err = snd_pcm_hw_rule_add(runtime, 0,
                                  SNDRV_PCM_HW_PARAM_RATE,
-                                 rule_rate, &runtime->hw,
+                                 rule_rate, dpcm,
                                  SNDRV_PCM_HW_PARAM_RATE, -1);
        if (err < 0)
                goto unlock;
        err = snd_pcm_hw_rule_add(runtime, 0,
                                  SNDRV_PCM_HW_PARAM_CHANNELS,
-                                 rule_channels, &runtime->hw,
+                                 rule_channels, dpcm,
                                  SNDRV_PCM_HW_PARAM_CHANNELS, -1);
        if (err < 0)
                goto unlock;
@@ -706,6 +730,10 @@ static int loopback_open(struct snd_pcm_substream *substream)
        else
                runtime->hw = cable->hw;
  unlock:
+       if (err < 0) {
+               free_cable(substream);
+               kfree(dpcm);
+       }
        mutex_unlock(&loopback->cable_lock);
        return err;
 }
@@ -714,20 +742,10 @@ static int loopback_close(struct snd_pcm_substream *substream)
 {
        struct loopback *loopback = substream->private_data;
        struct loopback_pcm *dpcm = substream->runtime->private_data;
-       struct loopback_cable *cable;
-       int dev = get_cable_index(substream);
 
-       loopback_timer_stop(dpcm);
+       loopback_timer_stop_sync(dpcm);
        mutex_lock(&loopback->cable_lock);
-       cable = loopback->cables[substream->number][dev];
-       if (cable->streams[!substream->stream]) {
-               /* other stream is still alive */
-               cable->streams[substream->stream] = NULL;
-       } else {
-               /* free the cable */
-               loopback->cables[substream->number][dev] = NULL;
-               kfree(cable);
-       }
+       free_cable(substream);
        mutex_unlock(&loopback->cable_lock);
        return 0;
 }