diff options
| author | Thomas Gleixner <tglx@linutronix.de> | 2010-03-12 11:34:14 -0500 |
|---|---|---|
| committer | Thomas Gleixner <tglx@linutronix.de> | 2010-03-12 13:10:29 -0500 |
| commit | 80a05b9ffa7dc13f6693902dd8999a2b61a3a0d7 (patch) | |
| tree | 5612eeb26804b135de66779d1a00eaf35a4ceca1 | |
| parent | ad6759fbf35d104dbf573cd6f4c6784ad6823f7e (diff) | |
clockevents: Sanitize min_delta_ns adjustment and prevent overflows
The current logic which handles clock events programming failures can
increase min_delta_ns unlimited and even can cause overflows.
Sanitize it by:
- prevent zero increase when min_delta_ns == 1
- limiting min_delta_ns to a jiffie
- bail out if the jiffie limit is hit
- add retries stats for /proc/timer_list so we can gather data
Reported-by: Uwe Kleine-Koenig <u.kleine-koenig@pengutronix.de>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
| -rw-r--r-- | include/linux/clockchips.h | 2 | ||||
| -rw-r--r-- | kernel/time/tick-oneshot.c | 52 | ||||
| -rw-r--r-- | kernel/time/timer_list.c | 3 |
3 files changed, 44 insertions, 13 deletions
diff --git a/include/linux/clockchips.h b/include/linux/clockchips.h index 0cf725bdd2a..fc53492b6ad 100644 --- a/include/linux/clockchips.h +++ b/include/linux/clockchips.h | |||
| @@ -73,6 +73,7 @@ enum clock_event_nofitiers { | |||
| 73 | * @list: list head for the management code | 73 | * @list: list head for the management code |
| 74 | * @mode: operating mode assigned by the management code | 74 | * @mode: operating mode assigned by the management code |
| 75 | * @next_event: local storage for the next event in oneshot mode | 75 | * @next_event: local storage for the next event in oneshot mode |
| 76 | * @retries: number of forced programming retries | ||
| 76 | */ | 77 | */ |
| 77 | struct clock_event_device { | 78 | struct clock_event_device { |
| 78 | const char *name; | 79 | const char *name; |
| @@ -93,6 +94,7 @@ struct clock_event_device { | |||
| 93 | struct list_head list; | 94 | struct list_head list; |
| 94 | enum clock_event_mode mode; | 95 | enum clock_event_mode mode; |
| 95 | ktime_t next_event; | 96 | ktime_t next_event; |
| 97 | unsigned long retries; | ||
| 96 | }; | 98 | }; |
| 97 | 99 | ||
| 98 | /* | 100 | /* |
diff --git a/kernel/time/tick-oneshot.c b/kernel/time/tick-oneshot.c index 0a8a213016f..aada0e52680 100644 --- a/kernel/time/tick-oneshot.c +++ b/kernel/time/tick-oneshot.c | |||
| @@ -22,6 +22,29 @@ | |||
| 22 | 22 | ||
| 23 | #include "tick-internal.h" | 23 | #include "tick-internal.h" |
| 24 | 24 | ||
| 25 | /* Limit min_delta to a jiffie */ | ||
| 26 | #define MIN_DELTA_LIMIT (NSEC_PER_SEC / HZ) | ||
| 27 | |||
| 28 | static int tick_increase_min_delta(struct clock_event_device *dev) | ||
| 29 | { | ||
| 30 | /* Nothing to do if we already reached the limit */ | ||
| 31 | if (dev->min_delta_ns >= MIN_DELTA_LIMIT) | ||
| 32 | return -ETIME; | ||
| 33 | |||
| 34 | if (dev->min_delta_ns < 5000) | ||
| 35 | dev->min_delta_ns = 5000; | ||
| 36 | else | ||
| 37 | dev->min_delta_ns += dev->min_delta_ns >> 1; | ||
| 38 | |||
| 39 | if (dev->min_delta_ns > MIN_DELTA_LIMIT) | ||
| 40 | dev->min_delta_ns = MIN_DELTA_LIMIT; | ||
| 41 | |||
| 42 | printk(KERN_WARNING "CE: %s increased min_delta_ns to %llu nsec\n", | ||
| 43 | dev->name ? dev->name : "?", | ||
| 44 | (unsigned long long) dev->min_delta_ns); | ||
| 45 | return 0; | ||
| 46 | } | ||
| 47 | |||
| 25 | /** | 48 | /** |
| 26 | * tick_program_event internal worker function | 49 | * tick_program_event internal worker function |
| 27 | */ | 50 | */ |
| @@ -37,23 +60,28 @@ int tick_dev_program_event(struct clock_event_device *dev, ktime_t expires, | |||
| 37 | if (!ret || !force) | 60 | if (!ret || !force) |
| 38 | return ret; | 61 | return ret; |
| 39 | 62 | ||
| 63 | dev->retries++; | ||
| 40 | /* | 64 | /* |
| 41 | * We tried 2 times to program the device with the given | 65 | * We tried 3 times to program the device with the given |
| 42 | * min_delta_ns. If that's not working then we double it | 66 | * min_delta_ns. If that's not working then we increase it |
| 43 | * and emit a warning. | 67 | * and emit a warning. |
| 44 | */ | 68 | */ |
| 45 | if (++i > 2) { | 69 | if (++i > 2) { |
| 46 | /* Increase the min. delta and try again */ | 70 | /* Increase the min. delta and try again */ |
| 47 | if (!dev->min_delta_ns) | 71 | if (tick_increase_min_delta(dev)) { |
| 48 | dev->min_delta_ns = 5000; | 72 | /* |
| 49 | else | 73 | * Get out of the loop if min_delta_ns |
| 50 | dev->min_delta_ns += dev->min_delta_ns >> 1; | 74 | * hit the limit already. That's |
| 51 | 75 | * better than staying here forever. | |
| 52 | printk(KERN_WARNING | 76 | * |
| 53 | "CE: %s increasing min_delta_ns to %llu nsec\n", | 77 | * We clear next_event so we have a |
| 54 | dev->name ? dev->name : "?", | 78 | * chance that the box survives. |
| 55 | (unsigned long long) dev->min_delta_ns << 1); | 79 | */ |
| 56 | 80 | printk(KERN_WARNING | |
| 81 | "CE: Reprogramming failure. Giving up\n"); | ||
| 82 | dev->next_event.tv64 = KTIME_MAX; | ||
| 83 | return -ETIME; | ||
| 84 | } | ||
| 57 | i = 0; | 85 | i = 0; |
| 58 | } | 86 | } |
| 59 | 87 | ||
diff --git a/kernel/time/timer_list.c b/kernel/time/timer_list.c index bdfb8dd1050..1a4a7dd7877 100644 --- a/kernel/time/timer_list.c +++ b/kernel/time/timer_list.c | |||
| @@ -228,6 +228,7 @@ print_tickdevice(struct seq_file *m, struct tick_device *td, int cpu) | |||
| 228 | SEQ_printf(m, " event_handler: "); | 228 | SEQ_printf(m, " event_handler: "); |
| 229 | print_name_offset(m, dev->event_handler); | 229 | print_name_offset(m, dev->event_handler); |
| 230 | SEQ_printf(m, "\n"); | 230 | SEQ_printf(m, "\n"); |
| 231 | SEQ_printf(m, " retries: %lu\n", dev->retries); | ||
| 231 | } | 232 | } |
| 232 | 233 | ||
| 233 | static void timer_list_show_tickdevices(struct seq_file *m) | 234 | static void timer_list_show_tickdevices(struct seq_file *m) |
| @@ -257,7 +258,7 @@ static int timer_list_show(struct seq_file *m, void *v) | |||
| 257 | u64 now = ktime_to_ns(ktime_get()); | 258 | u64 now = ktime_to_ns(ktime_get()); |
| 258 | int cpu; | 259 | int cpu; |
| 259 | 260 | ||
| 260 | SEQ_printf(m, "Timer List Version: v0.5\n"); | 261 | SEQ_printf(m, "Timer List Version: v0.6\n"); |
| 261 | SEQ_printf(m, "HRTIMER_MAX_CLOCK_BASES: %d\n", HRTIMER_MAX_CLOCK_BASES); | 262 | SEQ_printf(m, "HRTIMER_MAX_CLOCK_BASES: %d\n", HRTIMER_MAX_CLOCK_BASES); |
| 262 | SEQ_printf(m, "now at %Ld nsecs\n", (unsigned long long)now); | 263 | SEQ_printf(m, "now at %Ld nsecs\n", (unsigned long long)now); |
| 263 | 264 | ||
