blob: dd5cf9fdd5cdb0d206f188b22053e5e9a8b823a2 [file] [log] [blame]
Travis Geiselbrecht1d0df692008-09-01 02:26:09 -07001/*
Travis Geiselbrecht671cb792009-06-28 11:27:48 -07002 * Copyright (c) 2008-2009 Travis Geiselbrecht
Travis Geiselbrecht1d0df692008-09-01 02:26:09 -07003 *
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files
6 * (the "Software"), to deal in the Software without restriction,
7 * including without limitation the rights to use, copy, modify, merge,
8 * publish, distribute, sublicense, and/or sell copies of the Software,
9 * and to permit persons to whom the Software is furnished to do so,
10 * subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be
13 * included in all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 */
Travis Geiselbrecht12403e32010-05-06 13:36:57 -070023
24/**
25 * @file
26 * @brief Kernel timer subsystem
27 * @defgroup timer Timers
28 *
29 * The timer subsystem allows functions to be scheduled for later
30 * execution. Each timer object is used to cause one function to
31 * be executed at a later time.
32 *
33 * Timer callback functions are called in interrupt context.
34 *
35 * @{
36 */
Travis Geiselbrecht1d0df692008-09-01 02:26:09 -070037#include <debug.h>
38#include <list.h>
39#include <kernel/thread.h>
40#include <kernel/timer.h>
41#include <platform/timer.h>
42#include <platform.h>
43
44static struct list_node timer_queue;
45
Travis Geiselbrecht671cb792009-06-28 11:27:48 -070046static enum handler_return timer_tick(void *arg, time_t now);
47
Travis Geiselbrecht12403e32010-05-06 13:36:57 -070048/**
49 * @brief Initialize a timer object
50 */
Travis Geiselbrecht1d0df692008-09-01 02:26:09 -070051void timer_initialize(timer_t *timer)
52{
53 timer->magic = TIMER_MAGIC;
54 list_clear_node(&timer->node);
55 timer->scheduled_time = 0;
56 timer->periodic_time = 0;
57 timer->callback = 0;
58 timer->arg = 0;
59}
60
61static void insert_timer_in_queue(timer_t *timer)
62{
63 timer_t *entry;
64
Travis Geiselbrecht671cb792009-06-28 11:27:48 -070065// TRACEF("timer %p, scheduled %d, periodic %d\n", timer, timer->scheduled_time, timer->periodic_time);
66
Travis Geiselbrecht1d0df692008-09-01 02:26:09 -070067 list_for_every_entry(&timer_queue, entry, timer_t, node) {
Travis Geiselbrecht671cb792009-06-28 11:27:48 -070068 if (TIME_GT(entry->scheduled_time, timer->scheduled_time)) {
Travis Geiselbrecht1d0df692008-09-01 02:26:09 -070069 list_add_before(&entry->node, &timer->node);
70 return;
71 }
72 }
73
74 /* walked off the end of the list */
75 list_add_tail(&timer_queue, &timer->node);
76}
77
Travis Geiselbrecht671cb792009-06-28 11:27:48 -070078static void timer_set(timer_t *timer, time_t delay, time_t period, timer_callback callback, void *arg)
Travis Geiselbrecht1d0df692008-09-01 02:26:09 -070079{
80 time_t now;
81
Travis Geiselbrecht671cb792009-06-28 11:27:48 -070082// TRACEF("timer %p, delay %d, period %d, callback %p, arg %p, now %d\n", timer, delay, period, callback, arg);
Travis Geiselbrecht887061f2008-09-05 01:47:07 -070083
Travis Geiselbrecht1d0df692008-09-01 02:26:09 -070084 DEBUG_ASSERT(timer->magic == TIMER_MAGIC);
85
86 if (list_in_list(&timer->node)) {
87 panic("timer %p already in list\n", timer);
88 }
89
90 now = current_time();
91 timer->scheduled_time = now + delay;
Travis Geiselbrecht671cb792009-06-28 11:27:48 -070092 timer->periodic_time = period;
Travis Geiselbrecht1d0df692008-09-01 02:26:09 -070093 timer->callback = callback;
94 timer->arg = arg;
95
Travis Geiselbrecht887061f2008-09-05 01:47:07 -070096// TRACEF("scheduled time %u\n", timer->scheduled_time);
97
Travis Geiselbrecht1d0df692008-09-01 02:26:09 -070098 enter_critical_section();
99
100 insert_timer_in_queue(timer);
101
Travis Geiselbrecht671cb792009-06-28 11:27:48 -0700102#if PLATFORM_HAS_DYNAMIC_TIMER
103 if (list_peek_head_type(&timer_queue, timer_t, node) == timer) {
104 /* we just modified the head of the timer queue */
105// TRACEF("setting new timer for %u msecs\n", (uint)delay);
106 platform_set_oneshot_timer(timer_tick, NULL, delay);
107 }
108#endif
109
Travis Geiselbrecht1d0df692008-09-01 02:26:09 -0700110 exit_critical_section();
111}
112
Travis Geiselbrecht12403e32010-05-06 13:36:57 -0700113/**
114 * @brief Set up a timer that executes once
115 *
116 * This function specifies a callback function to be called after a specified
117 * delay. The function will be called one time.
118 *
119 * @param timer The timer to use
120 * @param delay The delay, in ms, before the timer is executed
121 * @param callback The function to call when the timer expires
122 * @param arg The argument to pass to the callback
123 *
124 * The timer function is declared as:
125 * enum handler_return callback(struct timer *, time_t now, void *arg) { ... }
126 */
Travis Geiselbrecht671cb792009-06-28 11:27:48 -0700127void timer_set_oneshot(timer_t *timer, time_t delay, timer_callback callback, void *arg)
128{
129 if (delay == 0)
130 delay = 1;
131 timer_set(timer, delay, 0, callback, arg);
132}
133
Travis Geiselbrecht12403e32010-05-06 13:36:57 -0700134/**
135 * @brief Set up a timer that executes repeatedly
136 *
137 * This function specifies a callback function to be called after a specified
138 * delay. The function will be called repeatedly.
139 *
140 * @param timer The timer to use
141 * @param delay The delay, in ms, before the timer is executed
142 * @param callback The function to call when the timer expires
143 * @param arg The argument to pass to the callback
144 *
145 * The timer function is declared as:
146 * enum handler_return callback(struct timer *, time_t now, void *arg) { ... }
147 */
Travis Geiselbrecht671cb792009-06-28 11:27:48 -0700148void timer_set_periodic(timer_t *timer, time_t period, timer_callback callback, void *arg)
149{
150 if (period == 0)
151 period = 1;
152 timer_set(timer, period, period, callback, arg);
153}
154
Travis Geiselbrecht12403e32010-05-06 13:36:57 -0700155/**
156 * @brief Cancel a pending timer
157 */
Travis Geiselbrecht1d0df692008-09-01 02:26:09 -0700158void timer_cancel(timer_t *timer)
159{
160 DEBUG_ASSERT(timer->magic == TIMER_MAGIC);
161
162 enter_critical_section();
163
Travis Geiselbrecht671cb792009-06-28 11:27:48 -0700164#if PLATFORM_HAS_DYNAMIC_TIMER
165 timer_t *oldhead = list_peek_head_type(&timer_queue, timer_t, node);
166#endif
167
Travis Geiselbrecht1d0df692008-09-01 02:26:09 -0700168 if (list_in_list(&timer->node))
169 list_delete(&timer->node);
170
Travis Geiselbrecht671cb792009-06-28 11:27:48 -0700171 /* to keep it from being reinserted into the queue if called from
172 * periodic timer callback.
173 */
174 timer->periodic_time = 0;
175 timer->callback = NULL;
176 timer->arg = NULL;
177
178#if PLATFORM_HAS_DYNAMIC_TIMER
179 /* see if we've just modified the head of the timer queue */
180 timer_t *newhead = list_peek_head_type(&timer_queue, timer_t, node);
181 if (newhead == NULL) {
182// TRACEF("clearing old hw timer, nothing in the queue\n");
183 platform_stop_timer();
184 } else if (newhead != oldhead) {
185 time_t delay;
186 time_t now = current_time();
187
188 if (TIME_LT(newhead->scheduled_time, now))
189 delay = 0;
190 else
191 delay = newhead->scheduled_time - now;
192
193// TRACEF("setting new timer to %d\n", delay);
194 platform_set_oneshot_timer(timer_tick, NULL, delay);
195 }
196#endif
197
Travis Geiselbrecht1d0df692008-09-01 02:26:09 -0700198 exit_critical_section();
199}
200
201/* called at interrupt time to process any pending timers */
202static enum handler_return timer_tick(void *arg, time_t now)
203{
204 timer_t *timer;
205 enum handler_return ret = INT_NO_RESCHEDULE;
206
207#if THREAD_STATS
208 thread_stats.timer_ints++;
209#endif
210
Travis Geiselbrecht671cb792009-06-28 11:27:48 -0700211// TRACEF("now %d\n", now);
212
Travis Geiselbrecht1d0df692008-09-01 02:26:09 -0700213 for (;;) {
214 /* see if there's an event to process */
215 timer = list_peek_head_type(&timer_queue, timer_t, node);
Travis Geiselbrecht671cb792009-06-28 11:27:48 -0700216 if (likely(!timer || TIME_LT(now, timer->scheduled_time)))
Travis Geiselbrecht1d0df692008-09-01 02:26:09 -0700217 break;
218
219 /* process it */
220 DEBUG_ASSERT(timer->magic == TIMER_MAGIC);
221 list_delete(&timer->node);
222// timer = list_remove_head_type(&timer_queue, timer_t, node);
223// ASSERT(timer);
224
Travis Geiselbrecht671cb792009-06-28 11:27:48 -0700225// TRACEF("dequeued timer %p, scheduled %d periodic %d\n", timer, timer->scheduled_time, timer->periodic_time);
226
Travis Geiselbrecht1d0df692008-09-01 02:26:09 -0700227#if THREAD_STATS
228 thread_stats.timers++;
229#endif
230
Travis Geiselbrecht671cb792009-06-28 11:27:48 -0700231 bool periodic = timer->periodic_time > 0;
232
233// TRACEF("timer %p firing callback %p, arg %p\n", timer, timer->callback, timer->arg);
Travis Geiselbrecht1d0df692008-09-01 02:26:09 -0700234 if (timer->callback(timer, now, timer->arg) == INT_RESCHEDULE)
235 ret = INT_RESCHEDULE;
Travis Geiselbrecht671cb792009-06-28 11:27:48 -0700236
237 /* if it was a periodic timer and it hasn't been requeued
238 * by the callback put it back in the list
239 */
240 if (periodic && !list_in_list(&timer->node) && timer->periodic_time > 0) {
241// TRACEF("periodic timer, period %u\n", (uint)timer->periodic_time);
242 timer->scheduled_time = now + timer->periodic_time;
243 insert_timer_in_queue(timer);
244 }
Travis Geiselbrecht1d0df692008-09-01 02:26:09 -0700245 }
246
Travis Geiselbrecht671cb792009-06-28 11:27:48 -0700247#if PLATFORM_HAS_DYNAMIC_TIMER
248 /* reset the timer to the next event */
249 timer = list_peek_head_type(&timer_queue, timer_t, node);
250 if (timer) {
251 /* has to be the case or it would have fired already */
252 ASSERT(TIME_GT(timer->scheduled_time, now));
253
254 time_t delay = timer->scheduled_time - now;
255
256// TRACEF("setting new timer for %u msecs for event %p\n", (uint)delay, timer);
257 platform_set_oneshot_timer(timer_tick, NULL, delay);
258 }
259#else
Travis Geiselbrecht1d0df692008-09-01 02:26:09 -0700260 /* let the scheduler have a shot to do quantum expiration, etc */
Travis Geiselbrecht671cb792009-06-28 11:27:48 -0700261 /* in case of dynamic timer, the scheduler will set up a periodic timer */
Travis Geiselbrecht1d0df692008-09-01 02:26:09 -0700262 if (thread_timer_tick() == INT_RESCHEDULE)
263 ret = INT_RESCHEDULE;
Travis Geiselbrecht671cb792009-06-28 11:27:48 -0700264#endif
Travis Geiselbrecht1d0df692008-09-01 02:26:09 -0700265
Travis Geiselbrecht671cb792009-06-28 11:27:48 -0700266 // XXX fix this, should return ret
Travis Geiselbrecht1d0df692008-09-01 02:26:09 -0700267 return INT_RESCHEDULE;
268}
269
270void timer_init(void)
271{
272 list_initialize(&timer_queue);
273
274 /* register for a periodic timer tick */
275 platform_set_periodic_timer(timer_tick, NULL, 10); /* 10ms */
276}
277
278