aboutsummaryrefslogtreecommitdiffstats
path: root/src/linux/timer.c
diff options
context:
space:
mode:
authorKevin O'Connor <kevin@koconnor.net>2017-07-20 00:02:43 -0400
committerKevin O'Connor <kevin@koconnor.net>2017-09-20 12:55:28 -0400
commitd851882278644b959d9175d6b303631ddc8cd2c6 (patch)
tree786412ef59997a3c885c4ee15cf28d2597fa2d77 /src/linux/timer.c
parent3ccecc568dbfd505fe3bdc46b4d16bf7a4528996 (diff)
downloadkutter-d851882278644b959d9175d6b303631ddc8cd2c6.tar.gz
kutter-d851882278644b959d9175d6b303631ddc8cd2c6.tar.xz
kutter-d851882278644b959d9175d6b303631ddc8cd2c6.zip
linux: Initial support for running Klipper in a Linux real-time process
Add support for compiling the Klipper micro-controller code as a real-time process capable of running on standard Linux systems. Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
Diffstat (limited to 'src/linux/timer.c')
-rw-r--r--src/linux/timer.c233
1 files changed, 233 insertions, 0 deletions
diff --git a/src/linux/timer.c b/src/linux/timer.c
new file mode 100644
index 00000000..016f3da3
--- /dev/null
+++ b/src/linux/timer.c
@@ -0,0 +1,233 @@
+// Handling of timers on linux systems
+//
+// Copyright (C) 2017 Kevin O'Connor <kevin@koconnor.net>
+//
+// This file may be distributed under the terms of the GNU GPLv3 license.
+
+#include <time.h> // struct timespec
+#include "autoconf.h" // CONFIG_CLOCK_FREQ
+#include "board/misc.h" // timer_from_us
+#include "board/irq.h" // irq_disable
+#include "basecmd.h" // stats_note_sleep
+#include "command.h" // DECL_CONSTANT
+#include "generic/timer_irq.h" // timer_dispatch_many
+#include "internal.h" // console_sleep
+#include "sched.h" // DECL_INIT
+
+
+/****************************************************************
+ * Timespec helpers
+ ****************************************************************/
+
+static uint32_t last_read_time_counter;
+static struct timespec last_read_time, next_wake_time;
+static time_t start_sec;
+
+#define NSECS 1000000000
+#define NSECS_PER_TICK (NSECS / CONFIG_CLOCK_FREQ)
+
+// Compare two 'struct timespec' times
+static inline uint8_t
+timespec_is_before(struct timespec ts1, struct timespec ts2)
+{
+ return (ts1.tv_sec < ts2.tv_sec
+ || (ts1.tv_sec == ts2.tv_sec && ts1.tv_nsec < ts2.tv_nsec));
+}
+
+// Convert a 'struct timespec' to a counter value
+static inline uint32_t
+timespec_to_time(struct timespec ts)
+{
+ return ((ts.tv_sec - start_sec) * CONFIG_CLOCK_FREQ
+ + ts.tv_nsec / NSECS_PER_TICK);
+}
+
+// Convert an internal time counter to a 'struct timespec'
+static inline struct timespec
+timespec_from_time(uint32_t time)
+{
+ int32_t counter_diff = time - last_read_time_counter;
+ struct timespec ts;
+ ts.tv_sec = last_read_time.tv_sec;
+ ts.tv_nsec = last_read_time.tv_nsec + counter_diff * NSECS_PER_TICK;
+ if ((unsigned long)ts.tv_nsec >= NSECS) {
+ if (ts.tv_nsec < 0) {
+ ts.tv_sec--;
+ ts.tv_nsec += NSECS;
+ } else {
+ ts.tv_sec++;
+ ts.tv_nsec -= NSECS;
+ }
+ }
+ return ts;
+}
+
+// Add a given number of nanoseconds to a 'struct timespec'
+static inline struct timespec
+timespec_add(struct timespec ts, long ns)
+{
+ ts.tv_nsec += ns;
+ if (ts.tv_nsec >= NSECS) {
+ ts.tv_sec++;
+ ts.tv_nsec -= NSECS;
+ }
+ return ts;
+}
+
+// Return the current time
+static struct timespec
+timespec_read(void)
+{
+ struct timespec ts;
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ return ts;
+}
+
+// Periodically update last_read_time / last_read_time_counter
+void
+timespec_update(void)
+{
+ last_read_time = timespec_read();
+ last_read_time_counter = timespec_to_time(last_read_time);
+}
+DECL_TASK(timespec_update);
+
+// Check if a given time has past
+int
+timer_check_periodic(struct timespec *ts)
+{
+ if (timespec_is_before(next_wake_time, *ts))
+ return 0;
+ *ts = next_wake_time;
+ ts->tv_sec += 2;
+ return 1;
+}
+
+
+/****************************************************************
+ * Timers
+ ****************************************************************/
+
+DECL_CONSTANT(CLOCK_FREQ, CONFIG_CLOCK_FREQ);
+
+// Return the number of clock ticks for a given number of microseconds
+uint32_t
+timer_from_us(uint32_t us)
+{
+ return us * (CONFIG_CLOCK_FREQ / 1000000);
+}
+
+// Return true if time1 is before time2. Always use this function to
+// compare times as regular C comparisons can fail if the counter
+// rolls over.
+uint8_t
+timer_is_before(uint32_t time1, uint32_t time2)
+{
+ return (int32_t)(time1 - time2) < 0;
+}
+
+// Return the current time (in clock ticks)
+uint32_t
+timer_read_time(void)
+{
+ return timespec_to_time(timespec_read());
+}
+
+// Activate timer dispatch as soon as possible
+void
+timer_kick(void)
+{
+ next_wake_time = last_read_time;
+}
+
+static struct timespec timer_repeat_until;
+#define TIMER_IDLE_REPEAT_NS 500000
+#define TIMER_REPEAT_NS 100000
+
+#define TIMER_MIN_TRY_NS 1000
+#define TIMER_DEFER_REPEAT_NS 5000
+
+// Invoke timers
+static void
+timer_dispatch(void)
+{
+ struct timespec tru = timer_repeat_until;
+ for (;;) {
+ // Run the next software timer
+ uint32_t next = sched_timer_dispatch();
+ struct timespec nt = timespec_from_time(next);
+
+ struct timespec now = timespec_read();
+ if (!timespec_is_before(nt, timespec_add(now, TIMER_MIN_TRY_NS))) {
+ // Schedule next timer normally.
+ next_wake_time = nt;
+ return;
+ }
+
+ if (unlikely(timespec_is_before(tru, now))) {
+ // Check if there are too many repeat timers
+ if (unlikely(timespec_is_before(timespec_add(nt, 100000000), now))
+ && !sched_is_shutdown())
+ shutdown("Rescheduled timer in the past");
+ if (sched_tasks_busy()) {
+ timer_repeat_until = timespec_add(now, TIMER_REPEAT_NS);
+ next_wake_time = timespec_add(now, TIMER_DEFER_REPEAT_NS);
+ return;
+ }
+ timer_repeat_until = timespec_add(now, TIMER_IDLE_REPEAT_NS);
+ }
+
+ // Next timer in the past or near future - wait for it to be ready
+ while (unlikely(timespec_is_before(now, nt)))
+ now = timespec_read();
+ }
+}
+
+void
+timer_init(void)
+{
+ start_sec = timespec_read().tv_sec;
+ timer_repeat_until.tv_sec = start_sec + 2;
+ timespec_update();
+ timer_kick();
+}
+DECL_INIT(timer_init);
+
+
+/****************************************************************
+ * Interrupt wrappers
+ ****************************************************************/
+
+void
+irq_disable(void)
+{
+}
+
+void
+irq_enable(void)
+{
+}
+
+irqstatus_t
+irq_save(void)
+{
+ return 0;
+}
+
+void
+irq_restore(irqstatus_t flag)
+{
+}
+
+void
+irq_wait(void)
+{
+ console_sleep(next_wake_time);
+}
+
+void
+irq_poll(void)
+{
+ if (!timespec_is_before(timespec_read(), next_wake_time))
+ timer_dispatch();
+}