Commit 1fb9b7d29d8e85ba3196eaa7ab871bf76fc98d36

Authored by Thomas Gleixner
1 parent 9c17bcda99

clockevents: prevent endless loop lockup

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>

Showing 3 changed files with 34 additions and 16 deletions Side-by-side Diff

kernel/time/tick-broadcast.c
... ... @@ -372,16 +372,8 @@
372 372 static int tick_broadcast_set_event(ktime_t expires, int force)
373 373 {
374 374 struct clock_event_device *bc = tick_broadcast_device.evtdev;
375   - ktime_t now = ktime_get();
376   - int res;
377 375  
378   - for(;;) {
379   - res = clockevents_program_event(bc, expires, now);
380   - if (!res || !force)
381   - return res;
382   - now = ktime_get();
383   - expires = ktime_add(now, ktime_set(0, bc->min_delta_ns));
384   - }
  376 + return tick_dev_program_event(bc, expires, force);
385 377 }
386 378  
387 379 int tick_resume_broadcast_oneshot(struct clock_event_device *bc)
kernel/time/tick-internal.h
... ... @@ -17,6 +17,8 @@
17 17 extern void tick_setup_oneshot(struct clock_event_device *newdev,
18 18 void (*handler)(struct clock_event_device *),
19 19 ktime_t nextevt);
  20 +extern int tick_dev_program_event(struct clock_event_device *dev,
  21 + ktime_t expires, int force);
20 22 extern int tick_program_event(ktime_t expires, int force);
21 23 extern void tick_oneshot_notify(void);
22 24 extern int tick_switch_to_oneshot(void (*handler)(struct clock_event_device *));
kernel/time/tick-oneshot.c
... ... @@ -25,18 +25,42 @@
25 25 /**
26 26 * tick_program_event internal worker function
27 27 */
28   -static int __tick_program_event(struct clock_event_device *dev,
29   - ktime_t expires, int force)
  28 +int tick_dev_program_event(struct clock_event_device *dev, ktime_t expires,
  29 + int force)
30 30 {
31 31 ktime_t now = ktime_get();
  32 + int i;
32 33  
33   - while (1) {
  34 + for (i = 0;;) {
34 35 int ret = clockevents_program_event(dev, expires, now);
35 36  
36 37 if (!ret || !force)
37 38 return ret;
  39 +
  40 + /*
  41 + * We tried 2 times to program the device with the given
  42 + * min_delta_ns. If that's not working then we double it
  43 + * and emit a warning.
  44 + */
  45 + if (++i > 2) {
  46 + printk(KERN_WARNING "CE: __tick_program_event of %s is "
  47 + "stuck %llx %llx\n", dev->name ? dev->name : "?",
  48 + now.tv64, expires.tv64);
  49 + printk(KERN_WARNING
  50 + "CE: increasing min_delta_ns %ld to %ld nsec\n",
  51 + dev->min_delta_ns, dev->min_delta_ns << 1);
  52 + WARN_ON(1);
  53 +
  54 + /* Double the min. delta and try again */
  55 + if (!dev->min_delta_ns)
  56 + dev->min_delta_ns = 5000;
  57 + else
  58 + dev->min_delta_ns <<= 1;
  59 + i = 0;
  60 + }
  61 +
38 62 now = ktime_get();
39   - expires = ktime_add(now, ktime_set(0, dev->min_delta_ns));
  63 + expires = ktime_add_ns(now, dev->min_delta_ns);
40 64 }
41 65 }
42 66  
... ... @@ -47,7 +71,7 @@
47 71 {
48 72 struct clock_event_device *dev = __get_cpu_var(tick_cpu_device).evtdev;
49 73  
50   - return __tick_program_event(dev, expires, force);
  74 + return tick_dev_program_event(dev, expires, force);
51 75 }
52 76  
53 77 /**
... ... @@ -71,7 +95,7 @@
71 95 {
72 96 newdev->event_handler = handler;
73 97 clockevents_set_mode(newdev, CLOCK_EVT_MODE_ONESHOT);
74   - __tick_program_event(newdev, next_event, 1);
  98 + tick_dev_program_event(newdev, next_event, 1);
75 99 }
76 100  
77 101 /**