From 05674a72f73cf8a72b8974b3659dfee267a939af Mon Sep 17 00:00:00 2001
From: Armin Luntzer <armin.luntzer@univie.ac.at>
Date: Tue, 5 Nov 2019 15:55:58 +0100
Subject: [PATCH] TICK: tick minimum timeout calibration

---
 kernel/tick.c | 277 +++++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 215 insertions(+), 62 deletions(-)

diff --git a/kernel/tick.c b/kernel/tick.c
index 582176d..46a926e 100644
--- a/kernel/tick.c
+++ b/kernel/tick.c
@@ -5,6 +5,8 @@
  *
  * @ingroup time
  *
+ * per-cpu tick device
+ *
  * @note this roughly follows the concept found in linux ticks
  */
 
@@ -17,35 +19,196 @@
 #include <kernel/clockevent.h>
 #include <kernel/kthread.h>
 #include <kernel/irq.h>
+#include <kernel/smp.h>
+
+#include <asm/processor.h>
 
 #define MSG "TICK: "
 
-/* the minimum effective tick period; default to 1 ms */
-static unsigned long tick_period_min_ns = 1000000UL;
 
-/* XXX */
-static struct clock_event_device *tick_device[2];
 
+/* XXX CPUS!
+ * maybe: enumerate CPUS, make pointer array from struct, terminate with NULL?
+ */
+static struct {
+	ktime         prev_cal_time;
+	unsigned long tick_period_min_ns;
+
+	struct clock_event_device *dev;
+
+} tick_device[2];
 
-#include <asm/processor.h>
 
-static void tick_event_handler(struct clock_event_device *dev)
+static void tick_calibrate_handler(struct clock_event_device *dev)
 {
-	/* does nothing, schedule later */
+	int cpu;
+
+	ktime now;
+	ktime delta;
+
+
+	cpu = smp_cpu_id();
+	now = ktime_get();
+
+	delta = ktime_delta(now, tick_device[cpu].prev_cal_time);
+
+	if (tick_device[cpu].prev_cal_time)
+		tick_device[cpu].tick_period_min_ns = (unsigned long) delta;
+
+	tick_device[cpu].prev_cal_time = now;
 }
 
 
-struct clock_event_device *tick_get_device(__attribute__((unused)) int cpu)
+/**
+ * @brief calibrate the minimum processable tick length for this device
+ *
+ * what this will do eventually:
+ *  - disable scheduling (maybe)
+ *  - mask all interrupts except timer (maybe)
+ *  - flush all caches before programming the timeoutii (we want worst-case times)
+ *  - in tick_event_handler, record time between calls
+ *  - keep decreasing tick length until time between calls does not decrement
+ *    (i.e. interrupt response limit has been hit)...or increase...
+ *  - NOTE: check clockevents_timout_in_range() or somesuch to clamp to
+ *	    actual timer range (maybe add function to clockevents to
+ *	    return actual timer minimum
+ *  - multiply tick length by some factor (2...10)
+ *  - ???
+ *  - profit!
+ */
+
+static void tick_calibrate_min(struct clock_event_device *dev)
 {
-	return tick_device[cpu];
+#define CALIBRATE_LOOPS 100
+
+	int cpu;
+	int i = 0;
+
+	unsigned long min;
+	unsigned long step;
+	unsigned long tick = 0;
+
+	ktime prev;
+
+
+	cpu = smp_cpu_id();
+
+	tick_device[cpu].tick_period_min_ns = 0;
+	tick_device[cpu].prev_cal_time      = 0;
+
+	/* we prefer one shot mode, but we'll grit our teeth, use periodic
+	 * and hope for the best, if the former is not supported
+	 */
+	if (tick_set_mode(TICK_MODE_ONESHOT)) {
+		if (tick_set_mode(TICK_MODE_PERIODIC)) {
+			/* this is some weird clock device... */
+			/* XXX should raise kernel alarm here */
+			return;
+		}
+	}
+
+	clockevents_set_handler(dev, tick_calibrate_handler);
+
+	step = ktime_get_readout_overhead();
+
+	prev = tick_device[cpu].prev_cal_time;
+
+	/* This should give us the minimum tick duration on first pass unless
+	 * the uptime clock has really bad resolution. If so, we'll increment
+	 * the timeout by the uptime clock readout overhead and try again.
+	 * This may not be as reliable if the clock device is in periodic
+	 * mode, but we should still get a somewhat sensible value.
+	 *
+	 * Note: the minimum effective tick period is typically in the order of
+	 * the interrupt processing time + some ISR overhead.
+	 *
+	 * XXX If there is a reboot/FDIR watchdog, make sure to enable it before
+	 *     initiating tick calibration, otherwise we could get stuck here,
+	 *     if the clock device does not actually function. We can't use
+	 *     a timeout here to catch this, since we're obviously in the
+	 *     process of initialising the very device...
+	 */
+
+	while (!tick_device[cpu].tick_period_min_ns) {
+
+		tick += step;
+
+		clockevents_program_timeout_ns(dev, tick);
+
+		while (prev == tick_device[cpu].prev_cal_time)
+			cpu_relax();
+
+		barrier(); /* prevent incorrect optimisation */
+
+		prev = tick_device[cpu].prev_cal_time;
+	}
+
+	/* ok, we found a tick timeout, let's do this a couple of times */
+	min = tick_device[cpu].tick_period_min_ns;
+
+	for (i = 1; i < CALIBRATE_LOOPS; i++) {
+
+		/* XXX should flush caches here, especially icache */
+
+		tick_device[cpu].tick_period_min_ns = 0;
+
+		clockevents_program_timeout_ns(dev, tick);
+
+		while (prev == tick_device[cpu].prev_cal_time)
+			cpu_relax();
+
+		barrier(); /* prevent incorrect optimisation */
+
+		/* something went wrong, we'll take we got so far and bail */
+		if (!tick_device[cpu].tick_period_min_ns) {
+
+			min /= i;
+			tick_device[cpu].tick_period_min_ns = min;
+
+			/* XXX raise a kernel alarm on partial calibration */
+			return;
+		}
+
+		prev = tick_device[cpu].prev_cal_time;
+
+		min += tick_device[cpu].tick_period_min_ns;
+
+		tick_device[cpu].tick_period_min_ns = 0;
+	}
+
+	min /= (i - 1);
+
+	/* to avoid sampling effects, we set this to at least 2x the minimum */
+	tick_device[cpu].tick_period_min_ns = min * 2;
+
+	pr_warn(MSG "calibrated minimum timeout of tick device to %d ns\n",
+		     tick_device[cpu].tick_period_min_ns);
+
+	clockevents_set_handler(dev, NULL);
+}
+
+
+/**
+ * @brief get the tick device for a given cpu
+ */
+
+static struct clock_event_device *tick_get_device(__attribute__((unused)) int cpu)
+{
+	return tick_device[cpu].dev;
 }
 
-void tick_set_device(struct clock_event_device *dev,
-					   __attribute__((unused)) int cpu)
+
+/**
+ * @brief set the tick device for a given cpu
+ */
+
+static void tick_set_device(struct clock_event_device *dev,
+			    __attribute__((unused)) int cpu)
 {
-	tick_device[cpu] = dev;
+	tick_device[cpu].dev = dev;
 }
 
+
 /**
  * @brief tick device selection check
  *
@@ -105,32 +268,6 @@ static int tick_set_mode_oneshot(struct clock_event_device *dev)
 	return 0;
 }
 
-/**
- * @brief calibrate the minimum processable tick length for this device
- *
- * XXX:
- * what this will do:
- *  - disable scheduling
- *  - mask all interrupts except timer (maybe)
- *  - in tick_event_handler, record time between calls
- *  - keep decreasing tick length until time between calls does not decrement
- *    (i.e. interrupt response limit has been hit)
- *  - NOTE: check clockevents_timout_in_range() or somesuch to clamp to
- *	    actual timer range (maybe add function to clockevents to
- *	    return actual timer minimum
- *  - multiply tick length by some factor (2...10)
- *  - ???
- *  - profit!
- */
-
-static void tick_calibrate_min(struct clock_event_device *dev)
-{
-#define RANDOM_TICK_RATE_NS	18000UL
-	tick_period_min_ns = RANDOM_TICK_RATE_NS;
-#define MIN_SLICE		100000UL
-	tick_period_min_ns = MIN_SLICE;
-}
-
 
 /**
  * @brief configure the tick device
@@ -138,14 +275,13 @@ static void tick_calibrate_min(struct clock_event_device *dev)
 
 static void tick_setup_device(struct clock_event_device *dev, int cpu)
 {
+	irq_set_affinity(dev->irq, cpu);
+
 	tick_calibrate_min(dev);
 
 	/* FIXME: assume blindly for the moment, should apply mode
 	 * of previous clock device (if replaced) */
 	tick_set_mode_periodic(dev);
-
-	clockevents_set_handler(dev, tick_event_handler);
-	clockevents_program_timeout_ns(dev, tick_period_min_ns);
 }
 
 
@@ -155,27 +291,27 @@ static void tick_setup_device(struct clock_event_device *dev, int cpu)
 
 void tick_check_device(struct clock_event_device *dev)
 {
+	int cpu;
+
 	struct clock_event_device *cur;
 
 
 	if (!dev)
 		return;
 
-	/* XXX need per-cpu selection later */
-	cur = tick_get_device(leon3_cpuid());
+
+	cpu = smp_cpu_id();
+
+	cur = tick_get_device(cpu);
 
 	if (!tick_check_preferred(cur, dev))
 		return;
 
 	clockevents_exchange_device(cur, dev);
 
-	/* XXX as above */
-	tick_set_device(dev, leon3_cpuid());
-
-	/* XXX as above */
-	tick_setup_device(dev, leon3_cpuid());
+	tick_set_device(dev, cpu);
 
-	irq_set_affinity(dev->irq, leon3_cpuid());
+	tick_setup_device(dev, cpu);
 
 	/* XXX should inform scheduler to recalculate any deadline-related
 	 * timeouts of tasks */
@@ -194,8 +330,7 @@ int tick_set_mode(enum tick_mode mode)
 	struct clock_event_device *dev;
 
 
-	/* XXX need per-cpu selection later */
-	dev = tick_get_device(leon3_cpuid());
+	dev = tick_get_device(smp_cpu_id());
 	if (!dev)
 		return -ENODEV;
 
@@ -221,33 +356,52 @@ int tick_set_mode(enum tick_mode mode)
 
 unsigned long tick_get_period_min_ns(void)
 {
-	return tick_period_min_ns;
+	return tick_device[smp_cpu_id()].tick_period_min_ns;
 }
 
 
-
 /**
- * @brief configure next tick period in nanoseconds
+ * @brief configure next tick period in nanoseconds for a cpu tick deivce
  *
  * returns 0 on success, 1 if nanoseconds range was clamped to clock range,
- *	   -ENODEV if no device is available for the current CPU
+ *	   -ENODEV if no device is available for the selected CPU
+ *
+ * @note if the tick period is smaller than the calibrated minimum tick period
+ *       of the timer, it will be clamped to the lower bound and a kernel alarm
+ *       will be raised
  */
 
-
-int tick_set_next_ns(unsigned long nanoseconds)
+int tick_set_next_ns_for_cpu(unsigned long nanoseconds, int cpu)
 {
 	struct clock_event_device *dev;
 
-
-	/* XXX need per-cpu selection later */
-	dev = tick_get_device(leon3_cpuid());
+	dev = tick_get_device(cpu);
 	if (!dev)
 		return -ENODEV;
 
+	if (nanoseconds < tick_device[smp_cpu_id()].tick_period_min_ns) {
+		nanoseconds = tick_device[smp_cpu_id()].tick_period_min_ns;
+
+		/* XXX should raise kernel alarm here */
+	}
+
 	return clockevents_program_timeout_ns(dev, nanoseconds);
 }
 
 
+/**
+ * @brief configure next tick period in nanoseconds
+ *
+ * returns 0 on success, 1 if nanoseconds range was clamped to clock range,
+ *	   -ENODEV if no device is available for the current CPU
+ */
+
+int tick_set_next_ns(unsigned long nanoseconds)
+{
+	return tick_set_next_ns_for_cpu(nanoseconds, smp_cpu_id());
+}
+
+
 /**
  * @brief configure next tick period in ktime
  *
@@ -265,8 +419,7 @@ int tick_set_next_ktime(struct timespec expires)
 	struct clock_event_device *dev;
 
 
-	/* XXX need per-cpu selection later */
-	dev = tick_get_device(leon3_cpuid());
+	dev = tick_get_device(smp_cpu_id());
 	if (!dev)
 		return -ENODEV;
 
-- 
GitLab