ALSA: timer: Fix link corruption due to double start or stop
[pandora-kernel.git] / sound / core / timer.c
index 8e7561d..39bf01d 100644 (file)
@@ -72,7 +72,7 @@ struct snd_timer_user {
        struct timespec tstamp;         /* trigger tstamp */
        wait_queue_head_t qchange_sleep;
        struct fasync_struct *fasync;
-       struct mutex tread_sem;
+       struct mutex ioctl_lock;
 };
 
 /* list of timers */
@@ -214,11 +214,13 @@ static void snd_timer_check_master(struct snd_timer_instance *master)
                    slave->slave_id == master->slave_id) {
                        list_move_tail(&slave->open_list, &master->slave_list_head);
                        spin_lock_irq(&slave_active_lock);
+                       spin_lock(&master->timer->lock);
                        slave->master = master;
                        slave->timer = master->timer;
                        if (slave->flags & SNDRV_TIMER_IFLG_RUNNING)
                                list_add_tail(&slave->active_list,
                                              &master->slave_active_head);
+                       spin_unlock(&master->timer->lock);
                        spin_unlock_irq(&slave_active_lock);
                }
        }
@@ -344,15 +346,18 @@ int snd_timer_close(struct snd_timer_instance *timeri)
                    timer->hw.close)
                        timer->hw.close(timer);
                /* remove slave links */
+               spin_lock_irq(&slave_active_lock);
+               spin_lock(&timer->lock);
                list_for_each_entry_safe(slave, tmp, &timeri->slave_list_head,
                                         open_list) {
-                       spin_lock_irq(&slave_active_lock);
-                       _snd_timer_stop(slave, 1, SNDRV_TIMER_EVENT_RESOLUTION);
                        list_move_tail(&slave->open_list, &snd_timer_slave_list);
                        slave->master = NULL;
                        slave->timer = NULL;
-                       spin_unlock_irq(&slave_active_lock);
+                       list_del_init(&slave->ack_list);
+                       list_del_init(&slave->active_list);
                }
+               spin_unlock(&timer->lock);
+               spin_unlock_irq(&slave_active_lock);
                mutex_unlock(&register_mutex);
        }
  out:
@@ -438,10 +443,17 @@ static int snd_timer_start_slave(struct snd_timer_instance *timeri)
        unsigned long flags;
 
        spin_lock_irqsave(&slave_active_lock, flags);
+       if (timeri->flags & SNDRV_TIMER_IFLG_RUNNING) {
+               spin_unlock_irqrestore(&slave_active_lock, flags);
+               return -EBUSY;
+       }
        timeri->flags |= SNDRV_TIMER_IFLG_RUNNING;
-       if (timeri->master)
+       if (timeri->master && timeri->timer) {
+               spin_lock(&timeri->timer->lock);
                list_add_tail(&timeri->active_list,
                              &timeri->master->slave_active_head);
+               spin_unlock(&timeri->timer->lock);
+       }
        spin_unlock_irqrestore(&slave_active_lock, flags);
        return 1; /* delayed start */
 }
@@ -459,18 +471,26 @@ int snd_timer_start(struct snd_timer_instance *timeri, unsigned int ticks)
                return -EINVAL;
        if (timeri->flags & SNDRV_TIMER_IFLG_SLAVE) {
                result = snd_timer_start_slave(timeri);
-               snd_timer_notify1(timeri, SNDRV_TIMER_EVENT_START);
+               if (result >= 0)
+                       snd_timer_notify1(timeri, SNDRV_TIMER_EVENT_START);
                return result;
        }
        timer = timeri->timer;
        if (timer == NULL)
                return -EINVAL;
        spin_lock_irqsave(&timer->lock, flags);
+       if (timeri->flags & (SNDRV_TIMER_IFLG_RUNNING |
+                            SNDRV_TIMER_IFLG_START)) {
+               result = -EBUSY;
+               goto unlock;
+       }
        timeri->ticks = timeri->cticks = ticks;
        timeri->pticks = 0;
        result = snd_timer_start1(timer, timeri, ticks);
+ unlock:
        spin_unlock_irqrestore(&timer->lock, flags);
-       snd_timer_notify1(timeri, SNDRV_TIMER_EVENT_START);
+       if (result >= 0)
+               snd_timer_notify1(timeri, SNDRV_TIMER_EVENT_START);
        return result;
 }
 
@@ -486,7 +506,13 @@ static int _snd_timer_stop(struct snd_timer_instance * timeri,
        if (timeri->flags & SNDRV_TIMER_IFLG_SLAVE) {
                if (!keep_flag) {
                        spin_lock_irqsave(&slave_active_lock, flags);
+                       if (!(timeri->flags & SNDRV_TIMER_IFLG_RUNNING)) {
+                               spin_unlock_irqrestore(&slave_active_lock, flags);
+                               return -EBUSY;
+                       }
                        timeri->flags &= ~SNDRV_TIMER_IFLG_RUNNING;
+                       list_del_init(&timeri->ack_list);
+                       list_del_init(&timeri->active_list);
                        spin_unlock_irqrestore(&slave_active_lock, flags);
                }
                goto __end;
@@ -495,6 +521,11 @@ static int _snd_timer_stop(struct snd_timer_instance * timeri,
        if (!timer)
                return -EINVAL;
        spin_lock_irqsave(&timer->lock, flags);
+       if (!(timeri->flags & (SNDRV_TIMER_IFLG_RUNNING |
+                              SNDRV_TIMER_IFLG_START))) {
+               spin_unlock_irqrestore(&timer->lock, flags);
+               return -EBUSY;
+       }
        list_del_init(&timeri->ack_list);
        list_del_init(&timeri->active_list);
        if ((timeri->flags & SNDRV_TIMER_IFLG_RUNNING) &&
@@ -560,10 +591,15 @@ int snd_timer_continue(struct snd_timer_instance *timeri)
        if (! timer)
                return -EINVAL;
        spin_lock_irqsave(&timer->lock, flags);
+       if (timeri->flags & SNDRV_TIMER_IFLG_RUNNING) {
+               result = -EBUSY;
+               goto unlock;
+       }
        if (!timeri->cticks)
                timeri->cticks = 1;
        timeri->pticks = 0;
        result = snd_timer_start1(timer, timeri, timer->sticks);
+ unlock:
        spin_unlock_irqrestore(&timer->lock, flags);
        snd_timer_notify1(timeri, SNDRV_TIMER_EVENT_CONTINUE);
        return result;
@@ -692,7 +728,7 @@ void snd_timer_interrupt(struct snd_timer * timer, unsigned long ticks_left)
                } else {
                        ti->flags &= ~SNDRV_TIMER_IFLG_RUNNING;
                        if (--timer->running)
-                               list_del(&ti->active_list);
+                               list_del_init(&ti->active_list);
                }
                if ((timer->hw.flags & SNDRV_TIMER_HW_TASKLET) ||
                    (ti->flags & SNDRV_TIMER_IFLG_FAST))
@@ -1255,7 +1291,7 @@ static int snd_timer_user_open(struct inode *inode, struct file *file)
                return -ENOMEM;
        spin_lock_init(&tu->qlock);
        init_waitqueue_head(&tu->qchange_sleep);
-       mutex_init(&tu->tread_sem);
+       mutex_init(&tu->ioctl_lock);
        tu->ticks = 1;
        tu->queue_size = 128;
        tu->queue = kmalloc(tu->queue_size * sizeof(struct snd_timer_read),
@@ -1275,8 +1311,10 @@ static int snd_timer_user_release(struct inode *inode, struct file *file)
        if (file->private_data) {
                tu = file->private_data;
                file->private_data = NULL;
+               mutex_lock(&tu->ioctl_lock);
                if (tu->timeri)
                        snd_timer_close(tu->timeri);
+               mutex_unlock(&tu->ioctl_lock);
                kfree(tu->queue);
                kfree(tu->tqueue);
                kfree(tu);
@@ -1514,7 +1552,6 @@ static int snd_timer_user_tselect(struct file *file,
        int err = 0;
 
        tu = file->private_data;
-       mutex_lock(&tu->tread_sem);
        if (tu->timeri) {
                snd_timer_close(tu->timeri);
                tu->timeri = NULL;
@@ -1558,7 +1595,6 @@ static int snd_timer_user_tselect(struct file *file,
        }
 
       __err:
-       mutex_unlock(&tu->tread_sem);
        return err;
 }
 
@@ -1771,7 +1807,7 @@ enum {
        SNDRV_TIMER_IOCTL_PAUSE_OLD = _IO('T', 0x23),
 };
 
-static long snd_timer_user_ioctl(struct file *file, unsigned int cmd,
+static long __snd_timer_user_ioctl(struct file *file, unsigned int cmd,
                                 unsigned long arg)
 {
        struct snd_timer_user *tu;
@@ -1788,17 +1824,11 @@ static long snd_timer_user_ioctl(struct file *file, unsigned int cmd,
        {
                int xarg;
 
-               mutex_lock(&tu->tread_sem);
-               if (tu->timeri) {       /* too late */
-                       mutex_unlock(&tu->tread_sem);
+               if (tu->timeri) /* too late */
                        return -EBUSY;
-               }
-               if (get_user(xarg, p)) {
-                       mutex_unlock(&tu->tread_sem);
+               if (get_user(xarg, p))
                        return -EFAULT;
-               }
                tu->tread = xarg ? 1 : 0;
-               mutex_unlock(&tu->tread_sem);
                return 0;
        }
        case SNDRV_TIMER_IOCTL_GINFO:
@@ -1831,6 +1861,18 @@ static long snd_timer_user_ioctl(struct file *file, unsigned int cmd,
        return -ENOTTY;
 }
 
+static long snd_timer_user_ioctl(struct file *file, unsigned int cmd,
+                                unsigned long arg)
+{
+       struct snd_timer_user *tu = file->private_data;
+       long ret;
+
+       mutex_lock(&tu->ioctl_lock);
+       ret = __snd_timer_user_ioctl(file, cmd, arg);
+       mutex_unlock(&tu->ioctl_lock);
+       return ret;
+}
+
 static int snd_timer_user_fasync(int fd, struct file * file, int on)
 {
        struct snd_timer_user *tu;