[PATCH] Fix wrong irq enable via rtc_control()
Takashi Iwai [Mon, 7 Nov 2005 10:14:57 +0000 (11:14 +0100)]
rtc_control() may be called in the interrupt context in ALSA rtc-timer
driver.  The patch fixes the wrong irq enable in rtc.c, and also fixes
the possible race of bit flags.

Signed-off-by: Takashi Iwai <tiwai@suse.de>

drivers/char/rtc.c

index 63fff7c..a7f099f 100644 (file)
@@ -149,8 +149,22 @@ static void get_rtc_alm_time (struct rtc_time *alm_tm);
 #ifdef RTC_IRQ
 static void rtc_dropped_irq(unsigned long data);
 
-static void set_rtc_irq_bit(unsigned char bit);
-static void mask_rtc_irq_bit(unsigned char bit);
+static void set_rtc_irq_bit_locked(unsigned char bit);
+static void mask_rtc_irq_bit_locked(unsigned char bit);
+
+static inline void set_rtc_irq_bit(unsigned char bit)
+{
+       spin_lock_irq(&rtc_lock);
+       set_rtc_irq_bit_locked(bit);
+       spin_unlock_irq(&rtc_lock);
+}
+
+static void mask_rtc_irq_bit(unsigned char bit)
+{
+       spin_lock_irq(&rtc_lock);
+       mask_rtc_irq_bit_locked(bit);
+       spin_unlock_irq(&rtc_lock);
+}
 #endif
 
 static int rtc_proc_open(struct inode *inode, struct file *file);
@@ -401,18 +415,19 @@ static int rtc_do_ioctl(unsigned int cmd, unsigned long arg, int kernel)
        }
        case RTC_PIE_OFF:       /* Mask periodic int. enab. bit */
        {
-               mask_rtc_irq_bit(RTC_PIE);
+               unsigned long flags; /* can be called from isr via rtc_control() */
+               spin_lock_irqsave (&rtc_lock, flags);
+               mask_rtc_irq_bit_locked(RTC_PIE);
                if (rtc_status & RTC_TIMER_ON) {
-                       spin_lock_irq (&rtc_lock);
                        rtc_status &= ~RTC_TIMER_ON;
                        del_timer(&rtc_irq_timer);
-                       spin_unlock_irq (&rtc_lock);
                }
+               spin_unlock_irqrestore (&rtc_lock, flags);
                return 0;
        }
        case RTC_PIE_ON:        /* Allow periodic ints          */
        {
-
+               unsigned long flags; /* can be called from isr via rtc_control() */
                /*
                 * We don't really want Joe User enabling more
                 * than 64Hz of interrupts on a multi-user machine.
@@ -421,14 +436,14 @@ static int rtc_do_ioctl(unsigned int cmd, unsigned long arg, int kernel)
                        (!capable(CAP_SYS_RESOURCE)))
                        return -EACCES;
 
+               spin_lock_irqsave (&rtc_lock, flags);
                if (!(rtc_status & RTC_TIMER_ON)) {
-                       spin_lock_irq (&rtc_lock);
                        rtc_irq_timer.expires = jiffies + HZ/rtc_freq + 2*HZ/100;
                        add_timer(&rtc_irq_timer);
                        rtc_status |= RTC_TIMER_ON;
-                       spin_unlock_irq (&rtc_lock);
                }
-               set_rtc_irq_bit(RTC_PIE);
+               set_rtc_irq_bit_locked(RTC_PIE);
+               spin_unlock_irqrestore (&rtc_lock, flags);
                return 0;
        }
        case RTC_UIE_OFF:       /* Mask ints from RTC updates.  */
@@ -609,6 +624,7 @@ static int rtc_do_ioctl(unsigned int cmd, unsigned long arg, int kernel)
        {
                int tmp = 0;
                unsigned char val;
+               unsigned long flags; /* can be called from isr via rtc_control() */
 
                /* 
                 * The max we can do is 8192Hz.
@@ -631,9 +647,9 @@ static int rtc_do_ioctl(unsigned int cmd, unsigned long arg, int kernel)
                if (arg != (1<<tmp))
                        return -EINVAL;
 
-               spin_lock_irq(&rtc_lock);
+               spin_lock_irqsave(&rtc_lock, flags);
                if (hpet_set_periodic_freq(arg)) {
-                       spin_unlock_irq(&rtc_lock);
+                       spin_unlock_irqrestore(&rtc_lock, flags);
                        return 0;
                }
                rtc_freq = arg;
@@ -641,7 +657,7 @@ static int rtc_do_ioctl(unsigned int cmd, unsigned long arg, int kernel)
                val = CMOS_READ(RTC_FREQ_SELECT) & 0xf0;
                val |= (16 - tmp);
                CMOS_WRITE(val, RTC_FREQ_SELECT);
-               spin_unlock_irq(&rtc_lock);
+               spin_unlock_irqrestore(&rtc_lock, flags);
                return 0;
        }
 #endif
@@ -844,12 +860,15 @@ int rtc_control(rtc_task_t *task, unsigned int cmd, unsigned long arg)
 #ifndef RTC_IRQ
        return -EIO;
 #else
-       spin_lock_irq(&rtc_task_lock);
+       unsigned long flags;
+       if (cmd != RTC_PIE_ON && cmd != RTC_PIE_OFF && cmd != RTC_IRQP_SET)
+               return -EINVAL;
+       spin_lock_irqsave(&rtc_task_lock, flags);
        if (rtc_callback != task) {
-               spin_unlock_irq(&rtc_task_lock);
+               spin_unlock_irqrestore(&rtc_task_lock, flags);
                return -ENXIO;
        }
-       spin_unlock_irq(&rtc_task_lock);
+       spin_unlock_irqrestore(&rtc_task_lock, flags);
        return rtc_do_ioctl(cmd, arg, 1);
 #endif
 }
@@ -1306,40 +1325,32 @@ static void get_rtc_alm_time(struct rtc_time *alm_tm)
  * meddles with the interrupt enable/disable bits.
  */
 
-static void mask_rtc_irq_bit(unsigned char bit)
+static void mask_rtc_irq_bit_locked(unsigned char bit)
 {
        unsigned char val;
 
-       spin_lock_irq(&rtc_lock);
-       if (hpet_mask_rtc_irq_bit(bit)) {
-               spin_unlock_irq(&rtc_lock);
+       if (hpet_mask_rtc_irq_bit(bit))
                return;
-       }
        val = CMOS_READ(RTC_CONTROL);
        val &=  ~bit;
        CMOS_WRITE(val, RTC_CONTROL);
        CMOS_READ(RTC_INTR_FLAGS);
 
        rtc_irq_data = 0;
-       spin_unlock_irq(&rtc_lock);
 }
 
-static void set_rtc_irq_bit(unsigned char bit)
+static void set_rtc_irq_bit_locked(unsigned char bit)
 {
        unsigned char val;
 
-       spin_lock_irq(&rtc_lock);
-       if (hpet_set_rtc_irq_bit(bit)) {
-               spin_unlock_irq(&rtc_lock);
+       if (hpet_set_rtc_irq_bit(bit))
                return;
-       }
        val = CMOS_READ(RTC_CONTROL);
        val |= bit;
        CMOS_WRITE(val, RTC_CONTROL);
        CMOS_READ(RTC_INTR_FLAGS);
 
        rtc_irq_data = 0;
-       spin_unlock_irq(&rtc_lock);
 }
 #endif