rtc-cmos: avoid spurious irqs
authorDavid Brownell <dbrownell@users.sourceforge.net>
Thu, 24 Jul 2008 04:30:47 +0000 (21:30 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Thu, 24 Jul 2008 17:47:34 +0000 (10:47 -0700)
This fixes kernel http://bugzilla.kernel.org/show_bug.cgi?id=11112 (bogus
RTC update IRQs reported) for rtc-cmos, in two ways:

  - When HPET is stealing the IRQs, use the first IRQ to grab
    the seconds counter which will be monitored (instead of
    using whatever was previously in that memory);

  - In sane IRQ handling modes, scrub out old IRQ status before
    enabling IRQs.

That latter is done by tightening up IRQ handling for rtc-cmos everywhere,
also ensuring that when HPET is used it's the only thing triggering IRQ
reports to userspace; net object shrink.

Also fix a bogus HPET message related to its RTC emulation.

Signed-off-by: David Brownell <dbrownell@users.sourceforge.net>
Report-by: W Unruh <unruh@physics.ubc.ca>
Cc: Andrew Victor <avictor.za@gmail.com>
Cc: <stable@kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
arch/x86/kernel/hpet.c
drivers/rtc/rtc-cmos.c

index 0ea6a19bfdfead682bbee695f165b05e9e4b9070..ad2b15a1334d919419b9040b52c2062dd5d3666d 100644 (file)
@@ -468,7 +468,7 @@ void hpet_disable(void)
 #define RTC_NUM_INTS           1
 
 static unsigned long hpet_rtc_flags;
-static unsigned long hpet_prev_update_sec;
+static int hpet_prev_update_sec;
 static struct rtc_time hpet_alarm_time;
 static unsigned long hpet_pie_count;
 static unsigned long hpet_t1_cmp;
@@ -575,6 +575,9 @@ int hpet_set_rtc_irq_bit(unsigned long bit_mask)
 
        hpet_rtc_flags |= bit_mask;
 
+       if ((bit_mask & RTC_UIE) && !(oldbits & RTC_UIE))
+               hpet_prev_update_sec = -1;
+
        if (!oldbits)
                hpet_rtc_timer_init();
 
@@ -652,7 +655,7 @@ static void hpet_rtc_timer_reinit(void)
                if (hpet_rtc_flags & RTC_PIE)
                        hpet_pie_count += lost_ints;
                if (printk_ratelimit())
-                       printk(KERN_WARNING "rtc: lost %d interrupts\n",
+                       printk(KERN_WARNING "hpet1: lost %d rtc interrupts\n",
                                lost_ints);
        }
 }
@@ -670,7 +673,8 @@ irqreturn_t hpet_rtc_interrupt(int irq, void *dev_id)
 
        if (hpet_rtc_flags & RTC_UIE &&
            curr_time.tm_sec != hpet_prev_update_sec) {
-               rtc_int_flag = RTC_UF;
+               if (hpet_prev_update_sec >= 0)
+                       rtc_int_flag = RTC_UF;
                hpet_prev_update_sec = curr_time.tm_sec;
        }
 
index e9984650ea950c8ca4ba388abb605dc54635688f..6ea349aba3ba1fa81f159bcbb18235073cf80164 100644 (file)
@@ -235,11 +235,56 @@ static int cmos_read_alarm(struct device *dev, struct rtc_wkalrm *t)
        return 0;
 }
 
+static void cmos_checkintr(struct cmos_rtc *cmos, unsigned char rtc_control)
+{
+       unsigned char   rtc_intr;
+
+       /* NOTE after changing RTC_xIE bits we always read INTR_FLAGS;
+        * allegedly some older rtcs need that to handle irqs properly
+        */
+       rtc_intr = CMOS_READ(RTC_INTR_FLAGS);
+
+       if (is_hpet_enabled())
+               return;
+
+       rtc_intr &= (rtc_control & RTC_IRQMASK) | RTC_IRQF;
+       if (is_intr(rtc_intr))
+               rtc_update_irq(cmos->rtc, 1, rtc_intr);
+}
+
+static void cmos_irq_enable(struct cmos_rtc *cmos, unsigned char mask)
+{
+       unsigned char   rtc_control;
+
+       /* flush any pending IRQ status, notably for update irqs,
+        * before we enable new IRQs
+        */
+       rtc_control = CMOS_READ(RTC_CONTROL);
+       cmos_checkintr(cmos, rtc_control);
+
+       rtc_control |= mask;
+       CMOS_WRITE(rtc_control, RTC_CONTROL);
+       hpet_set_rtc_irq_bit(mask);
+
+       cmos_checkintr(cmos, rtc_control);
+}
+
+static void cmos_irq_disable(struct cmos_rtc *cmos, unsigned char mask)
+{
+       unsigned char   rtc_control;
+
+       rtc_control = CMOS_READ(RTC_CONTROL);
+       rtc_control &= ~mask;
+       CMOS_WRITE(rtc_control, RTC_CONTROL);
+       hpet_mask_rtc_irq_bit(mask);
+
+       cmos_checkintr(cmos, rtc_control);
+}
+
 static int cmos_set_alarm(struct device *dev, struct rtc_wkalrm *t)
 {
        struct cmos_rtc *cmos = dev_get_drvdata(dev);
        unsigned char   mon, mday, hrs, min, sec;
-       unsigned char   rtc_control, rtc_intr;
 
        if (!is_valid_irq(cmos->irq))
                return -EIO;
@@ -266,15 +311,7 @@ static int cmos_set_alarm(struct device *dev, struct rtc_wkalrm *t)
        spin_lock_irq(&rtc_lock);
 
        /* next rtc irq must not be from previous alarm setting */
-       rtc_control = CMOS_READ(RTC_CONTROL);
-       rtc_control &= ~RTC_AIE;
-       CMOS_WRITE(rtc_control, RTC_CONTROL);
-       hpet_mask_rtc_irq_bit(RTC_AIE);
-
-       rtc_intr = CMOS_READ(RTC_INTR_FLAGS);
-       rtc_intr &= (rtc_control & RTC_IRQMASK) | RTC_IRQF;
-       if (is_intr(rtc_intr))
-               rtc_update_irq(cmos->rtc, 1, rtc_intr);
+       cmos_irq_disable(cmos, RTC_AIE);
 
        /* update alarm */
        CMOS_WRITE(hrs, RTC_HOURS_ALARM);
@@ -293,16 +330,8 @@ static int cmos_set_alarm(struct device *dev, struct rtc_wkalrm *t)
         */
        hpet_set_alarm_time(t->time.tm_hour, t->time.tm_min, t->time.tm_sec);
 
-       if (t->enabled) {
-               rtc_control |= RTC_AIE;
-               CMOS_WRITE(rtc_control, RTC_CONTROL);
-               hpet_set_rtc_irq_bit(RTC_AIE);
-
-               rtc_intr = CMOS_READ(RTC_INTR_FLAGS);
-               rtc_intr &= (rtc_control & RTC_IRQMASK) | RTC_IRQF;
-               if (is_intr(rtc_intr))
-                       rtc_update_irq(cmos->rtc, 1, rtc_intr);
-       }
+       if (t->enabled)
+               cmos_irq_enable(cmos, RTC_AIE);
 
        spin_unlock_irq(&rtc_lock);
 
@@ -335,28 +364,17 @@ static int cmos_irq_set_freq(struct device *dev, int freq)
 static int cmos_irq_set_state(struct device *dev, int enabled)
 {
        struct cmos_rtc *cmos = dev_get_drvdata(dev);
-       unsigned char   rtc_control, rtc_intr;
        unsigned long   flags;
 
        if (!is_valid_irq(cmos->irq))
                return -ENXIO;
 
        spin_lock_irqsave(&rtc_lock, flags);
-       rtc_control = CMOS_READ(RTC_CONTROL);
-
-       if (enabled) {
-               rtc_control |= RTC_PIE;
-               hpet_set_rtc_irq_bit(RTC_PIE);
-       } else {
-               rtc_control &= ~RTC_PIE;
-               hpet_mask_rtc_irq_bit(RTC_PIE);
-       }
-       CMOS_WRITE(rtc_control, RTC_CONTROL);
 
-       rtc_intr = CMOS_READ(RTC_INTR_FLAGS);
-       rtc_intr &= (rtc_control & RTC_IRQMASK) | RTC_IRQF;
-       if (is_intr(rtc_intr))
-               rtc_update_irq(cmos->rtc, 1, rtc_intr);
+       if (enabled)
+               cmos_irq_enable(cmos, RTC_PIE);
+       else
+               cmos_irq_disable(cmos, RTC_PIE);
 
        spin_unlock_irqrestore(&rtc_lock, flags);
        return 0;
@@ -368,7 +386,6 @@ static int
 cmos_rtc_ioctl(struct device *dev, unsigned int cmd, unsigned long arg)
 {
        struct cmos_rtc *cmos = dev_get_drvdata(dev);
-       unsigned char   rtc_control, rtc_intr;
        unsigned long   flags;
 
        switch (cmd) {
@@ -385,32 +402,20 @@ cmos_rtc_ioctl(struct device *dev, unsigned int cmd, unsigned long arg)
        }
 
        spin_lock_irqsave(&rtc_lock, flags);
-       rtc_control = CMOS_READ(RTC_CONTROL);
        switch (cmd) {
        case RTC_AIE_OFF:       /* alarm off */
-               rtc_control &= ~RTC_AIE;
-               hpet_mask_rtc_irq_bit(RTC_AIE);
+               cmos_irq_disable(cmos, RTC_AIE);
                break;
        case RTC_AIE_ON:        /* alarm on */
-               rtc_control |= RTC_AIE;
-               hpet_set_rtc_irq_bit(RTC_AIE);
+               cmos_irq_enable(cmos, RTC_AIE);
                break;
        case RTC_UIE_OFF:       /* update off */
-               rtc_control &= ~RTC_UIE;
-               hpet_mask_rtc_irq_bit(RTC_UIE);
+               cmos_irq_disable(cmos, RTC_UIE);
                break;
        case RTC_UIE_ON:        /* update on */
-               rtc_control |= RTC_UIE;
-               hpet_set_rtc_irq_bit(RTC_UIE);
+               cmos_irq_enable(cmos, RTC_UIE);
                break;
        }
-       CMOS_WRITE(rtc_control, RTC_CONTROL);
-
-       rtc_intr = CMOS_READ(RTC_INTR_FLAGS);
-       rtc_intr &= (rtc_control & RTC_IRQMASK) | RTC_IRQF;
-       if (is_intr(rtc_intr))
-               rtc_update_irq(cmos->rtc, 1, rtc_intr);
-
        spin_unlock_irqrestore(&rtc_lock, flags);
        return 0;
 }
@@ -571,7 +576,6 @@ static irqreturn_t cmos_interrupt(int irq, void *p)
         * alarm woke the system.
         */
        if (irqstat & RTC_AIE) {
-               rtc_control = CMOS_READ(RTC_CONTROL);
                rtc_control &= ~RTC_AIE;
                CMOS_WRITE(rtc_control, RTC_CONTROL);
                hpet_mask_rtc_irq_bit(RTC_AIE);
@@ -685,17 +689,10 @@ cmos_do_probe(struct device *dev, struct resource *ports, int rtc_irq)
        hpet_set_periodic_freq(cmos_rtc.rtc->irq_freq);
        CMOS_WRITE(RTC_REF_CLCK_32KHZ | 0x06, RTC_FREQ_SELECT);
 
-       /* disable irqs.
-        *
-        * NOTE after changing RTC_xIE bits we always read INTR_FLAGS;
-        * allegedly some older rtcs need that to handle irqs properly
-        */
-       rtc_control = CMOS_READ(RTC_CONTROL);
-       rtc_control &= ~(RTC_PIE | RTC_AIE | RTC_UIE);
-       CMOS_WRITE(rtc_control, RTC_CONTROL);
-       hpet_mask_rtc_irq_bit(RTC_PIE | RTC_AIE | RTC_UIE);
+       /* disable irqs */
+       cmos_irq_disable(&cmos_rtc, RTC_PIE | RTC_AIE | RTC_UIE);
 
-       CMOS_READ(RTC_INTR_FLAGS);
+       rtc_control = CMOS_READ(RTC_CONTROL);
 
        spin_unlock_irq(&rtc_lock);
 
@@ -768,15 +765,8 @@ cleanup0:
 
 static void cmos_do_shutdown(void)
 {
-       unsigned char   rtc_control;
-
        spin_lock_irq(&rtc_lock);
-       rtc_control = CMOS_READ(RTC_CONTROL);
-       rtc_control &= ~RTC_IRQMASK;
-       CMOS_WRITE(rtc_control, RTC_CONTROL);
-       hpet_mask_rtc_irq_bit(RTC_IRQMASK);
-
-       CMOS_READ(RTC_INTR_FLAGS);
+       cmos_irq_disable(&cmos_rtc, RTC_IRQMASK);
        spin_unlock_irq(&rtc_lock);
 }
 
@@ -817,7 +807,6 @@ static int cmos_suspend(struct device *dev, pm_message_t mesg)
        spin_lock_irq(&rtc_lock);
        cmos->suspend_ctrl = tmp = CMOS_READ(RTC_CONTROL);
        if (tmp & (RTC_PIE|RTC_AIE|RTC_UIE)) {
-               unsigned char   irqstat;
                unsigned char   mask;
 
                if (do_wake)
@@ -828,10 +817,7 @@ static int cmos_suspend(struct device *dev, pm_message_t mesg)
                CMOS_WRITE(tmp, RTC_CONTROL);
                hpet_mask_rtc_irq_bit(mask);
 
-               irqstat = CMOS_READ(RTC_INTR_FLAGS);
-               irqstat &= (tmp & RTC_IRQMASK) | RTC_IRQF;
-               if (is_intr(irqstat))
-                       rtc_update_irq(cmos->rtc, 1, irqstat);
+               cmos_checkintr(cmos, tmp);
        }
        spin_unlock_irq(&rtc_lock);
 
@@ -875,7 +861,7 @@ static int cmos_resume(struct device *dev)
 
                        mask = CMOS_READ(RTC_INTR_FLAGS);
                        mask &= (tmp & RTC_IRQMASK) | RTC_IRQF;
-                       if (!is_intr(mask))
+                       if (!is_hpet_enabled() || !is_intr(mask))
                                break;
 
                        /* force one-shot behavior if HPET blocked