From 751cfe6475d99150e9ca24ccf3f33d9fe482f3aa Mon Sep 17 00:00:00 2001
From: Ludwig Ortmann <ludwig.ortmann@fu-berlin.de>
Date: Fri, 20 Dec 2013 16:52:32 +0100
Subject: [PATCH] fix native hwtimer

The hardware timer used relative offsets that were never updated
before. This leads to two problems:
a) later timers will get pushed into the future by the amount of
   previous timers
b) if a short timer is set continuously, a longer timer will never be
   called

Example:
a)
Timer a with 500 ms is set, timer b with 600 ms is set.
timer a expires after 500 ms, timer b will be set to expire in 600 ms
which totals to 1100 ms.
b)
Timer a is set to 500 ms, timer b is set to 600 ms.
Timer a expires and is set again. Now timer a will expire in 500 ms
and timer b will be pushed further into the future. Repeating this
will lead to b never expiring.
---
 cpu/native/hwtimer_cpu.c | 82 +++++++++++++++++++++++++++++++---------
 1 file changed, 65 insertions(+), 17 deletions(-)

diff --git a/cpu/native/hwtimer_cpu.c b/cpu/native/hwtimer_cpu.c
index 7a1532a2bd..ba23db9344 100644
--- a/cpu/native/hwtimer_cpu.c
+++ b/cpu/native/hwtimer_cpu.c
@@ -31,6 +31,7 @@
 #include <signal.h>
 #include <stdint.h>
 #include <stdlib.h>
+#include <string.h>
 #include <err.h>
 
 #include "hwtimer.h"
@@ -56,6 +57,41 @@ static int native_hwtimer_isset[ARCH_MAXTIMERS];
 static int next_timer = -1;
 static void (*int_handler)(int);
 
+
+/**
+ * Subtract the `struct timeval' values x and y, storing the result in
+ * result.
+ * Return 1 if the difference is negative, otherwise 0.
+ *
+ * Source:
+ * http://www.gnu.org/software/libc/manual/html_node/Elapsed-Time.html
+ */
+int timeval_subtract(struct timeval *result, struct timeval *x, struct timeval *y)
+{
+    /* Perform the carry for the later subtraction by updating y. */
+    if (x->tv_usec < y->tv_usec) {
+        int nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1;
+        y->tv_usec -= 1000000 * nsec;
+        y->tv_sec += nsec;
+    }
+    if (x->tv_usec - y->tv_usec > 1000000) {
+        int nsec = (x->tv_usec - y->tv_usec) / 1000000;
+        y->tv_usec += 1000000 * nsec;
+        y->tv_sec -= nsec;
+    }
+
+    /**
+     * Compute the time remaining to wait.
+     * tv_usec is certainly positive.
+     */
+    result->tv_sec = x->tv_sec - y->tv_sec;
+    result->tv_usec = x->tv_usec - y->tv_usec;
+
+    /* Return 1 if result is negative. */
+    return x->tv_sec < y->tv_sec;
+}
+
+
 /**
  * sets timeval to given ticks
  */
@@ -114,8 +150,25 @@ void schedule_timer(void)
     }
 
     /* next pending timer is in slot next_timer */
-    if (setitimer(ITIMER_REAL, &native_hwtimer[next_timer], NULL) == -1) {
-        err(EXIT_FAILURE, "schedule_timer");
+    struct timeval now;
+    ticks2tv(native_hwtimer_now, &now);
+
+    struct itimerval result;
+    memset(&result, 0, sizeof(result));
+
+    int retval = timeval_subtract(&result.it_value, &native_hwtimer[next_timer].it_value, &now);
+    if (retval || (tv2ticks(&result.it_value) < HWTIMERMINOFFSET)) {
+        /* the timeout has happened already, schedule an interrupt */
+        int sig = SIGALRM;
+        if (real_write(_sig_pipefd[1], &sig, sizeof(int)) == -1) {
+            err(EXIT_FAILURE, "schedule_timer(): real_write()");
+        }
+        _native_sigpend++;
+        return;
+    }
+
+    if (setitimer(ITIMER_REAL, &result, NULL) == -1) {
+        err(EXIT_FAILURE, "schedule_timer: setitimer");
     }
     else {
         DEBUG("schedule_timer(): set next timer (%i).\n", next_timer);
@@ -184,19 +237,8 @@ void hwtimer_arch_set(unsigned long offset, short timer)
 {
     DEBUG("hwtimer_arch_set(%lu, \033[31m%i\033[0m)\n", offset, timer);
 
-    if (offset < HWTIMERMINOFFSET) {
-        offset = HWTIMERMINOFFSET;
-        DEBUG("hwtimer_arch_set: offset < MIN, set to: %lu\n", offset);
-    }
-
-    native_hwtimer_isset[timer] = 1;
-
-    ticks2tv(offset, &(native_hwtimer[timer].it_value));
-    DEBUG("hwtimer_arch_set(): that is %lu s %lu us from now\n",
-          (unsigned long)native_hwtimer[timer].it_value.tv_sec,
-          (unsigned long)native_hwtimer[timer].it_value.tv_usec);
-
-    schedule_timer();
+    offset += native_hwtimer_now;
+    hwtimer_arch_set_absolute(offset, timer);
 
     return;
 }
@@ -204,9 +246,15 @@ void hwtimer_arch_set(unsigned long offset, short timer)
 void hwtimer_arch_set_absolute(unsigned long value, short timer)
 {
     DEBUG("hwtimer_arch_set_absolute(%lu, %i)\n", value, timer);
-    value -= native_hwtimer_now;
 
-    hwtimer_arch_set(value, timer);
+    ticks2tv(value, &(native_hwtimer[timer].it_value));
+
+    DEBUG("hwtimer_arch_set_absolute(): that is %lu s %lu us from now\n",
+          (unsigned long)native_hwtimer[timer].it_value.tv_sec,
+          (unsigned long)native_hwtimer[timer].it_value.tv_usec);
+
+    native_hwtimer_isset[timer] = 1;
+    schedule_timer();
 
     return;
 }
-- 
GitLab