aboutsummaryrefslogtreecommitdiffstats
path: root/timer.c
blob: c80e9606809911bb3c0bb65fe592d3532c7022b9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
/* $Id$
 * $URL$
 *
 * generic timer handling
 *
 * Copyright (C) 2003, 2004 Michael Reinelt <michael@reinelt.co.at>
 * Copyright (C) 2004 The LCD4Linux Team <lcd4linux-devel@users.sourceforge.net>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

/*
 * exported functions:
 *
 * int timer_add(void (*callback) (void *data), void *data, const int
 *     interval, const int one_shot)
 *
 *   Create a new timer and add it to the timer queue.
 *
 *
 * int timer_add_late(void (*callback) (void *data), void *data, const
 *     int interval, const int one_shot)
 *
 *   This function creates a new timer and adds it to the timer queue
 *   just as timer_add() does, but the timer will NOT be triggered
 *   immediately (useful for scheduling things).
 *
 *
 * int timer_process (struct timespec *delay)
 *
 *   process timer queue
 *
 *
 * int timer_remove(void (*callback) (void *data), void *data)
 *
 *   Remove a new timer with given callback and data.
 *
 *
 * void timer_exit(void)
 *
 *   Release all timers and free the associated memory block.
 *
 */


#include "config.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <time.h>

#include "debug.h"
#include "cfg.h"
#include "timer.h"

#ifdef WITH_DMALLOC
#include <dmalloc.h>
#endif

#define CLOCK_SKEW_DETECT_TIME_IN_S 1

typedef struct TIMER {
    void (*callback) (void *data);
    void *data;
    struct timeval when;
    int interval;
    int one_shot;
    int active;
} TIMER;


/* number of allocated timer slots */
int nTimers = 0;

/* pointer to memory used for storing the timer slots */
TIMER *Timers = NULL;


static void timer_inc(struct timeval *tv, const int interval)
/*  Update a timer's trigger by adding the given interval.

    tv (timeval pointer): struct holding current time

	interval (integer): interval in milliseconds to be added to the
	timer's trigger

	return value: void */
{
    struct timeval diff = {
	.tv_sec = interval / 1000,
	.tv_usec = (interval % 1000) * 1000
    };

    timeradd(tv, &diff, tv);
}


int timer_remove(void (*callback) (void *data), void *data)
/*  Remove a new timer with given callback and data.

    callback (void pointer): function of type callback(void *data);
    here, it will be used to identify the timer

	data (void pointer): data which will be passed to the callback
	function; here, it will be used to identify the timer

	return value (integer): returns a value of 0 on successful timer
	deletion; otherwise returns a value of -1 */
{
    int i;			/* current timer's ID */

    /* loop through the timer slots and try to find the specified
       timer slot by looking for its settings */
    for (i = 0; i < nTimers; i++) {
	if (Timers[i].callback == callback && Timers[i].data == data && Timers[i].active) {
	    /* we have found the timer slot, so mark it as being inactive;
	       we will not actually delete the timer, so its memory may be
	       re-used */
	    Timers[i].active = 0;
	    return 0;
	}
    }
    /* we have NOT found the timer slot, so signal failure by
       returning a value of -1 */
    return -1;
}


int timer_add(void (*callback) (void *data), void *data, const int interval, const int one_shot)
/*  Create a new timer and add it to the timer queue.

    callback (void pointer): function of type callback(void *data)
	which will be called whenever the timer triggers; this pointer
	will also be used to identify a specific timer

	data (void pointer): data which will be passed to the callback
	function; this pointer will also be used to identify a specific
	timer

	interval (integer): specifies the timer's triggering interval in
	milliseconds

	one_shot (integer): specifies whether the timer should trigger
	indefinitely until it is deleted (value of 0) or only once (all
	other values)

	return value (integer): returns a value of 0 on successful timer
	creation; otherwise returns a value of -1 */
{
    int i;			/* current timer's ID */
    struct timeval now;		/* struct to hold current time */

    /* try to minimize memory usage by looping through the timer slots
       and looking for an inactive timer */
    for (i = 0; i < nTimers; i++) {
	if (Timers[i].active == 0)
	    /* we've just found one, so let's reuse it ("i" holds its ID)
	       by breaking the loop */
	    break;
    }

    /* no inactive timers found, so we have to add a new timer slot */
    if (i >= nTimers) {
	/* increment number of timers and (re-)allocate memory used for
	   storing the timer slots */
	nTimers++;
	Timers = realloc(Timers, nTimers * sizeof(*Timers));

	/* realloc() has failed */
	if (Timers == NULL) {
	    /* restore old number of timers and signal unsuccessful timer
	       creation */
	    nTimers--;
	    return -1;
	}
    }

    /* get current time so the timer triggers immediately */
    gettimeofday(&now, NULL);

    /* fill in timer data */
    Timers[i].callback = callback;
    Timers[i].data = data;
    Timers[i].when = now;
    Timers[i].interval = interval;
    Timers[i].one_shot = one_shot;

    /* set timer to active so that it is processed and not overwritten
       by the memory optimisation above */
    Timers[i].active = 1;

    /* one-shot timers should NOT fire immediately, so delay them by a
       single timer interval */
    if (one_shot) {
	timer_inc(&Timers[i].when, interval);
    }

    /* signal successful timer creation */
    return 0;
}


int timer_add_late(void (*callback) (void *data), void *data, const int interval, const int one_shot)
/*  This function creates a new timer and adds it to the timer queue
	just as timer_add() does, but the timer will NOT be triggered
	immediately (useful for scheduling things).

    callback (void pointer): function of type callback(void *data)
	which will be called whenever the timer triggers; this pointer
	will also be used to identify a specific timer

	data (void pointer): data which will be passed to the callback
	function; this pointer will also be used to identify a specific
	timer

	interval (integer): specifies the timer's triggering interval in
	milliseconds

	one_shot (integer): specifies whether the timer should trigger
	indefinitely until it is deleted (value of 0) or only once (all
	other values)

	return value (integer): returns a value of 0 on successful timer
	creation; otherwise returns a value of -1 */
{
    /* create new timer slot and add it to the timer queue; mask it as
       one-shot timer for now, so the timer will be delayed by a
       single timer interval */
    if (!timer_add(callback, data, interval, 1)) {
	/* signal unsuccessful timer creation */
	return -1;
    }

    int i;			/* current timer's ID */

    /* loop through the timer slots and try to find the new timer slot
       by looking for its settings */
    for (i = 0; i < nTimers; i++) {
	if (Timers[i].callback == callback && Timers[i].data == data && Timers[i].active
	    && Timers[i].interval == interval) {
	    /* we have found the new timer slot, so unmask it by setting
	       its "one_shot" variable to the REAL value; then signal
	       successful timer creation */
	    Timers[i].one_shot = one_shot;
	    return 0;
	}
    }

    /* we have NOT found the new timer slot for some reason, so signal
       failure by returning a value of -1 */
    return -1;
}


int timer_process(struct timespec *delay)
{
    int i, min;
    struct timeval now;

    /* the current moment */
    gettimeofday(&now, NULL);

    /* sanity check */
    if (nTimers == 0) {
	error("huh? not one single timer to process? dazed and confused...");
	return -1;
    }

    /* process expired timers */
    for (i = 0; i < nTimers; i++) {
	if (Timers[i].active == 0)
	    continue;
	if (!timercmp(&Timers[i].when, &now, >)) {
	    /* callback */
	    if (Timers[i].callback != NULL) {
		Timers[i].callback(Timers[i].data);
	    }
	    /* respawn or delete timer */
	    if (Timers[i].one_shot) {
		Timers[i].active = 0;
	    } else {
		timer_inc(&Timers[i].when, Timers[i].interval);
	    }
	}
    }

    /* find next timer */
    min = -1;
    for (i = 0; i < nTimers; i++) {
	if (Timers[i].active == 0)
	    continue;
	if ((min < 0) || timercmp(&Timers[i].when, &Timers[min].when, <))
	    min = i;
    }

    if (min < 0) {
	error("huh? not one single timer left? dazed and confused...");
	return -1;
    }

    /* update the current moment to compensate for processing delay */
    gettimeofday(&now, NULL);

    /* delay until next timer event */
    struct timeval diff;
    timersub(&Timers[min].when, &now, &diff);

    /* for negative delays, directly trigger next update */
    if (diff.tv_sec < 0)
	timerclear(&diff);

    delay->tv_sec = diff.tv_sec;
    /* microseconds to nanoseconds!! */
    delay->tv_nsec = diff.tv_usec * 1000;

    /* check if date changed */
    if ((delay->tv_sec) > CLOCK_SKEW_DETECT_TIME_IN_S) {
	delay->tv_sec = 0;
	delay->tv_nsec = 0;
	info("Oops, clock skewed, update timestamp");
	gettimeofday(&now, NULL);
	Timers[min].when = now;
    }

    return 0;

}


void timer_exit(void)
/*  Release all timers and free the associated memory block.

	return value: void */
{
    /* reset number of allocated timer slots */
    nTimers = 0;

    if (Timers != NULL) {
	/* free memory used for storing the timer slots */
	free(Timers);
	Timers = NULL;
    }
}