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.
This commit is contained in:
Ludwig Ortmann 2013-12-20 16:52:32 +01:00
parent b0d51d326a
commit 751cfe6475

View File

@ -31,6 +31,7 @@
#include <signal.h> #include <signal.h>
#include <stdint.h> #include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
#include <err.h> #include <err.h>
#include "hwtimer.h" #include "hwtimer.h"
@ -56,6 +57,41 @@ static int native_hwtimer_isset[ARCH_MAXTIMERS];
static int next_timer = -1; static int next_timer = -1;
static void (*int_handler)(int); 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 * sets timeval to given ticks
*/ */
@ -114,8 +150,25 @@ void schedule_timer(void)
} }
/* next pending timer is in slot next_timer */ /* next pending timer is in slot next_timer */
if (setitimer(ITIMER_REAL, &native_hwtimer[next_timer], NULL) == -1) { struct timeval now;
err(EXIT_FAILURE, "schedule_timer"); 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 { else {
DEBUG("schedule_timer(): set next timer (%i).\n", next_timer); 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); DEBUG("hwtimer_arch_set(%lu, \033[31m%i\033[0m)\n", offset, timer);
if (offset < HWTIMERMINOFFSET) { offset += native_hwtimer_now;
offset = HWTIMERMINOFFSET; hwtimer_arch_set_absolute(offset, timer);
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();
return; return;
} }
@ -204,9 +246,15 @@ void hwtimer_arch_set(unsigned long offset, short timer)
void hwtimer_arch_set_absolute(unsigned long value, short timer) void hwtimer_arch_set_absolute(unsigned long value, short timer)
{ {
DEBUG("hwtimer_arch_set_absolute(%lu, %i)\n", value, 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; return;
} }