Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 80a05b9f authored by Thomas Gleixner's avatar Thomas Gleixner
Browse files

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: default avatarUwe Kleine-Koenig <u.kleine-koenig@pengutronix.de>
Signed-off-by: default avatarThomas Gleixner <tglx@linutronix.de>
parent ad6759fb
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -73,6 +73,7 @@ enum clock_event_nofitiers {
 * @list:		list head for the management code
 * @mode:		operating mode assigned by the management code
 * @next_event:		local storage for the next event in oneshot mode
 * @retries:		number of forced programming retries
 */
struct clock_event_device {
	const char		*name;
@@ -93,6 +94,7 @@ struct clock_event_device {
	struct list_head	list;
	enum clock_event_mode	mode;
	ktime_t			next_event;
	unsigned long		retries;
};

/*
+40 −12
Original line number Diff line number Diff line
@@ -22,6 +22,29 @@

#include "tick-internal.h"

/* Limit min_delta to a jiffie */
#define MIN_DELTA_LIMIT		(NSEC_PER_SEC / HZ)

static int tick_increase_min_delta(struct clock_event_device *dev)
{
	/* Nothing to do if we already reached the limit */
	if (dev->min_delta_ns >= MIN_DELTA_LIMIT)
		return -ETIME;

	if (dev->min_delta_ns < 5000)
		dev->min_delta_ns = 5000;
	else
		dev->min_delta_ns += dev->min_delta_ns >> 1;

	if (dev->min_delta_ns > MIN_DELTA_LIMIT)
		dev->min_delta_ns = MIN_DELTA_LIMIT;

	printk(KERN_WARNING "CE: %s increased min_delta_ns to %llu nsec\n",
	       dev->name ? dev->name : "?",
	       (unsigned long long) dev->min_delta_ns);
	return 0;
}

/**
 * tick_program_event internal worker function
 */
@@ -37,23 +60,28 @@ int tick_dev_program_event(struct clock_event_device *dev, ktime_t expires,
		if (!ret || !force)
			return ret;

		dev->retries++;
		/*
		 * We tried 2 times to program the device with the given
		 * min_delta_ns. If that's not working then we double it
		 * We tried 3 times to program the device with the given
		 * min_delta_ns. If that's not working then we increase it
		 * and emit a warning.
		 */
		if (++i > 2) {
			/* Increase the min. delta and try again */
			if (!dev->min_delta_ns)
				dev->min_delta_ns = 5000;
			else
				dev->min_delta_ns += dev->min_delta_ns >> 1;

			if (tick_increase_min_delta(dev)) {
				/*
				 * Get out of the loop if min_delta_ns
				 * hit the limit already. That's
				 * better than staying here forever.
				 *
				 * We clear next_event so we have a
				 * chance that the box survives.
				 */
				printk(KERN_WARNING
			       "CE: %s increasing min_delta_ns to %llu nsec\n",
			       dev->name ? dev->name : "?",
			       (unsigned long long) dev->min_delta_ns << 1);

				       "CE: Reprogramming failure. Giving up\n");
				dev->next_event.tv64 = KTIME_MAX;
				return -ETIME;
			}
			i = 0;
		}

+2 −1
Original line number Diff line number Diff line
@@ -228,6 +228,7 @@ print_tickdevice(struct seq_file *m, struct tick_device *td, int cpu)
	SEQ_printf(m, " event_handler:  ");
	print_name_offset(m, dev->event_handler);
	SEQ_printf(m, "\n");
	SEQ_printf(m, " retries:        %lu\n", dev->retries);
}

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)
	u64 now = ktime_to_ns(ktime_get());
	int cpu;

	SEQ_printf(m, "Timer List Version: v0.5\n");
	SEQ_printf(m, "Timer List Version: v0.6\n");
	SEQ_printf(m, "HRTIMER_MAX_CLOCK_BASES: %d\n", HRTIMER_MAX_CLOCK_BASES);
	SEQ_printf(m, "now at %Ld nsecs\n", (unsigned long long)now);