clockevents: prevent endless loop lockup
authorThomas Gleixner <tglx@linutronix.de>
Wed, 3 Sep 2008 21:37:14 +0000 (21:37 +0000)
committerThomas Gleixner <tglx@linutronix.de>
Fri, 5 Sep 2008 09:11:53 +0000 (11:11 +0200)
The C1E/HPET bug reports on AMDX2/RS690 systems where tracked down to a
too small value of the HPET minumum delta for programming an event.

The clockevents code needs to enforce an interrupt event on the clock event
device in some cases. The enforcement code was stupid and naive, as it just
added the minimum delta to the current time and tried to reprogram the device.
When the minimum delta is too small, then this loops forever.

Add a sanity check. Allow reprogramming to fail 3 times, then print a warning
and double the minimum delta value to make sure, that this does not happen again.
Use the same function for both tick-oneshot and tick-broadcast code.

Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
kernel/time/tick-broadcast.c
kernel/time/tick-internal.h
kernel/time/tick-oneshot.c

index 5744f40b26970cc3e928b3b9b3cf28bf2038710a..2bc1f046151ce67efe02840c986643003774b2ee 100644 (file)
@@ -372,16 +372,8 @@ cpumask_t *tick_get_broadcast_oneshot_mask(void)
 static int tick_broadcast_set_event(ktime_t expires, int force)
 {
        struct clock_event_device *bc = tick_broadcast_device.evtdev;
-       ktime_t now = ktime_get();
-       int res;
-
-       for(;;) {
-               res = clockevents_program_event(bc, expires, now);
-               if (!res || !force)
-                       return res;
-               now = ktime_get();
-               expires = ktime_add(now, ktime_set(0, bc->min_delta_ns));
-       }
+
+       return tick_dev_program_event(bc, expires, force);
 }
 
 int tick_resume_broadcast_oneshot(struct clock_event_device *bc)
index f13f2b7f4fd46bda983d7138edce267b1a9e0c91..0ffc2918ea6fb8365523ad0a63266cd68628b65f 100644 (file)
@@ -17,6 +17,8 @@ extern void tick_handle_periodic(struct clock_event_device *dev);
 extern void tick_setup_oneshot(struct clock_event_device *newdev,
                               void (*handler)(struct clock_event_device *),
                               ktime_t nextevt);
+extern int tick_dev_program_event(struct clock_event_device *dev,
+                                 ktime_t expires, int force);
 extern int tick_program_event(ktime_t expires, int force);
 extern void tick_oneshot_notify(void);
 extern int tick_switch_to_oneshot(void (*handler)(struct clock_event_device *));
index 06595c64b0c99ac6869c02eebe9da8e317bf5204..2e35501e61ddaa5b65a4275875931bc1fb438395 100644 (file)
 /**
  * tick_program_event internal worker function
  */
-static int __tick_program_event(struct clock_event_device *dev,
-                               ktime_t expires, int force)
+int tick_dev_program_event(struct clock_event_device *dev, ktime_t expires,
+                          int force)
 {
        ktime_t now = ktime_get();
+       int i;
 
-       while (1) {
+       for (i = 0;;) {
                int ret = clockevents_program_event(dev, expires, now);
 
                if (!ret || !force)
                        return ret;
+
+               /*
+                * We tried 2 times to program the device with the given
+                * min_delta_ns. If that's not working then we double it
+                * and emit a warning.
+                */
+               if (++i > 2) {
+                       printk(KERN_WARNING "CE: __tick_program_event of %s is "
+                              "stuck %llx %llx\n", dev->name ? dev->name : "?",
+                              now.tv64, expires.tv64);
+                       printk(KERN_WARNING
+                              "CE: increasing min_delta_ns %ld to %ld nsec\n",
+                              dev->min_delta_ns, dev->min_delta_ns << 1);
+                       WARN_ON(1);
+
+                       /* Double the min. delta and try again */
+                       if (!dev->min_delta_ns)
+                               dev->min_delta_ns = 5000;
+                       else
+                               dev->min_delta_ns <<= 1;
+                       i = 0;
+               }
+
                now = ktime_get();
-               expires = ktime_add(now, ktime_set(0, dev->min_delta_ns));
+               expires = ktime_add_ns(now, dev->min_delta_ns);
        }
 }
 
@@ -47,7 +71,7 @@ int tick_program_event(ktime_t expires, int force)
 {
        struct clock_event_device *dev = __get_cpu_var(tick_cpu_device).evtdev;
 
-       return __tick_program_event(dev, expires, force);
+       return tick_dev_program_event(dev, expires, force);
 }
 
 /**
@@ -71,7 +95,7 @@ void tick_setup_oneshot(struct clock_event_device *newdev,
 {
        newdev->event_handler = handler;
        clockevents_set_mode(newdev, CLOCK_EVT_MODE_ONESHOT);
-       __tick_program_event(newdev, next_event, 1);
+       tick_dev_program_event(newdev, next_event, 1);
 }
 
 /**