summaryrefslogtreecommitdiff
path: root/src/timer.c
blob: a9fc6f7b104bd9046429370c640a17e6f9980bc2 (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
/*
 * Copyright (c) 2017 Richard Braun.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

#include <lib/list.h>
#include <lib/macros.h>

#include "cpu.h"
#include "mutex.h"
#include "panic.h"
#include "thread.h"
#include "timer.h"

#define TIMER_STACK_SIZE 4096

/*
 * When determining whether a point in time is in the future or the past,
 * it's important to remember that the value is always finite. Here,
 * the ticks use a 32-bits type (unsigned long on i386), so, assuming
 * a 100Hz frequency, time wraps around about every 497 days. Therefore,
 * the implementation needs a way to partition time between the future
 * and the past. To do so, it considers that all values from a reference
 * up to a threshold are in the future (except the present), and all other
 * values are in the past. This macro defines that threshold.
 *
 * Currently, the threshold is half of the maximum value, which is
 * equivalent to using a signed integer where positive values would denote
 * the future and negative ones the past, except that overflows on signed
 * integers are undefined behaviour, whereas overflows on unsigned
 * integers are well specified (3.4.3 Undefined Behaviour and 6.2.5 Types).
 */
#define TIMER_THRESHOLD (((unsigned long)-1) / 2)

/*
 * The current time, in ticks.
 */
static unsigned long timer_ticks;

/*
 * List of timers, sorted by time.
 *
 * The timer mutex must be locked when accessing the timer list.
 */
static struct list timer_list;
static struct mutex timer_mutex;

/*
 * The timer thread, which provides context for all timer callbacks.
 */
static struct thread *timer_thread;

/*
 * True if the timer list is empty.
 *
 * This is a copy of list_empty(&timer_list) which may be used from
 * interrupt context, where locking a mutex is impossible.
 *
 * Interrupts must be disabled when accessing this variable.
 */
static bool timer_list_empty;

/*
 * Time in ticks of the first timer on the timer list.
 *
 * Only valid if the list isn't empty.
 *
 * Interrupts must be disabled when accessing this variable.
 */
static unsigned long timer_wakeup_ticks;

bool
timer_ticks_expired(unsigned long ticks, unsigned long ref)
{
    return (ticks - ref) > TIMER_THRESHOLD;
}

bool
timer_ticks_occurred(unsigned long ticks, unsigned long ref)
{
    return (ticks == ref) || timer_ticks_expired(ticks, ref);
}

static bool
timer_work_pending(void)
{
    assert(!cpu_intr_enabled());

    return !timer_list_empty
           && timer_ticks_occurred(timer_wakeup_ticks, timer_ticks);
}

static bool
timer_scheduled(const struct timer *timer)
{
    return !list_node_unlinked(&timer->node);
}

static bool
timer_expired(const struct timer *timer, unsigned long ref)
{
    return timer_ticks_expired(timer->ticks, ref);
}

static bool
timer_occurred(const struct timer *timer, unsigned long ref)
{
    return timer_ticks_occurred(timer->ticks, ref);
}

static void
timer_process(struct timer *timer)
{
    timer->fn(timer->arg);
}

static void
timer_process_list(unsigned long now)
{
    struct timer *timer;
    uint32_t eflags;

    mutex_lock(&timer_mutex);

    while (!list_empty(&timer_list)) {
        timer = list_first_entry(&timer_list, typeof(*timer), node);

        if (!timer_occurred(timer, now)) {
            break;
        }

        list_remove(&timer->node);
        list_node_init(&timer->node);
        mutex_unlock(&timer_mutex);

        timer_process(timer);

        mutex_lock(&timer_mutex);
    }

    eflags = cpu_intr_save();

    timer_list_empty = list_empty(&timer_list);

    if (!timer_list_empty) {
        timer = list_first_entry(&timer_list, typeof(*timer), node);
        timer_wakeup_ticks = timer->ticks;
    }

    cpu_intr_restore(eflags);

    mutex_unlock(&timer_mutex);
}

static void
timer_run(void *arg)
{
    unsigned long now;
    uint32_t eflags;

    (void)arg;

    for (;;) {
        thread_preempt_disable();
        eflags = cpu_intr_save();

        for (;;) {
            now = timer_ticks;

            if (timer_work_pending()) {
                break;
            }

            thread_sleep();
        }

        cpu_intr_restore(eflags);
        thread_preempt_enable();

        timer_process_list(now);
    }
}

void
timer_setup(void)
{
    int error;

    timer_ticks = 0;
    timer_list_empty = true;

    list_init(&timer_list);
    mutex_init(&timer_mutex);

    error = thread_create(&timer_thread, timer_run, NULL,
                          "timer", TIMER_STACK_SIZE, THREAD_MAX_PRIORITY);

    if (error) {
        panic("timer: unable to create thread");
    }
}

unsigned long
timer_now(void)
{
    unsigned long ticks;
    uint32_t eflags;

    eflags = cpu_intr_save();
    ticks = timer_ticks;
    cpu_intr_restore(eflags);

    return ticks;
}

void
timer_init(struct timer *timer, timer_fn_t fn, void *arg)
{
    list_node_init(&timer->node);
    timer->fn = fn;
    timer->arg = arg;
}

unsigned long
timer_get_time(const struct timer *timer)
{
    unsigned long ticks;

    mutex_lock(&timer_mutex);
    ticks = timer->ticks;
    mutex_unlock(&timer_mutex);

    return ticks;
}

void
timer_schedule(struct timer *timer, unsigned long ticks)
{
    struct timer *tmp;
    uint32_t eflags;

    mutex_lock(&timer_mutex);

    assert(!timer_scheduled(timer));

    timer->ticks = ticks;

    /*
     * Find the insertion point,
     *
     * This makes timer scheduling an O(n) operation, and assumes a low
     * number of timers. This is also why a mutex is used instead of
     * disabling preemption, since preemption then remains enabled,
     * allowing higher priority threads to run.
     *
     * [1] https://en.wikipedia.org/wiki/Big_O_notation
     */
    list_for_each_entry(&timer_list, tmp, node) {
        if (!timer_expired(tmp, ticks)) {
            break;
        }
    }

    list_insert_before(&timer->node, &tmp->node);

    timer = list_first_entry(&timer_list, typeof(*timer), node);

    /*
     * This interrupt-based critical section could be moved outside the
     * mutex-based one, which would make the latter shorter. The downside
     * of this approach is that a timer interrupt occurring after unlocking
     * the mutex but before disabling interrupts will wake up the timer
     * thread, if there was work pending already. The timer thread may
     * preempt the current thread and process all timers before the current
     * thread resumes and clears the list empty flag. At the next timer
     * interrupt, the handler will determine that there is work pending and
     * wake up the timer thread, despite the fact that there is actually no
     * timer in the list.
     *
     * By holding the mutex while clearing the list empty flag, potential
     * spurious wake-ups are completely avoided.
     */
    eflags = cpu_intr_save();
    timer_list_empty = false;
    timer_wakeup_ticks = timer->ticks;
    cpu_intr_restore(eflags);

    mutex_unlock(&timer_mutex);
}

void
timer_report_tick(void)
{
    timer_ticks++;

    if (timer_work_pending()) {
        thread_wakeup(timer_thread);
    }
}