1 /*
2 * Copyright 2002-2011, Haiku. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Copyright 2001, Travis Geiselbrecht. All rights reserved.
6 * Distributed under the terms of the NewOS License.
7 */
8
9
10 /*! Policy info for timers */
11
12
13 #include <timer.h>
14
15 #include <OS.h>
16
17 #include <arch/timer.h>
18 #include <boot/kernel_args.h>
19 #include <cpu.h>
20 #include <debug.h>
21 #include <elf.h>
22 #include <real_time_clock.h>
23 #include <smp.h>
24 #include <thread.h>
25 #include <util/AutoLock.h>
26
27
28 struct per_cpu_timer_data {
29 spinlock lock;
30 timer* events;
31 timer* current_event;
32 int32 current_event_in_progress;
33 bigtime_t real_time_offset;
34 };
35
36 static per_cpu_timer_data sPerCPU[SMP_MAX_CPUS];
37
38
39 //#define TRACE_TIMER
40 #ifdef TRACE_TIMER
41 # define TRACE(x) dprintf x
42 #else
43 # define TRACE(x) ;
44 #endif
45
46
47 /*! Sets the hardware timer to the given absolute time.
48
49 \param scheduleTime The absolute system time for the timer expiration.
50 \param now The current system time.
51 */
52 static void
set_hardware_timer(bigtime_t scheduleTime,bigtime_t now)53 set_hardware_timer(bigtime_t scheduleTime, bigtime_t now)
54 {
55 arch_timer_set_hardware_timer(scheduleTime > now ? scheduleTime - now : 0);
56 }
57
58
59 /*! Sets the hardware timer to the given absolute time.
60
61 \param scheduleTime The absolute system time for the timer expiration.
62 */
63 static inline void
set_hardware_timer(bigtime_t scheduleTime)64 set_hardware_timer(bigtime_t scheduleTime)
65 {
66 set_hardware_timer(scheduleTime, system_time());
67 }
68
69
70 /*! NOTE: expects the list to be locked. */
71 static void
add_event_to_list(timer * event,timer ** list)72 add_event_to_list(timer* event, timer** list)
73 {
74 timer* next;
75 timer* previous = NULL;
76
77 for (next = *list; next != NULL; previous = next, next = previous->next) {
78 if ((bigtime_t)next->schedule_time >= (bigtime_t)event->schedule_time)
79 break;
80 }
81
82 event->next = next;
83 if (previous != NULL)
84 previous->next = event;
85 else
86 *list = event;
87 }
88
89
90 static void
per_cpu_real_time_clock_changed(void *,int cpu)91 per_cpu_real_time_clock_changed(void*, int cpu)
92 {
93 per_cpu_timer_data& cpuData = sPerCPU[cpu];
94 SpinLocker cpuDataLocker(cpuData.lock);
95
96 bigtime_t realTimeOffset = rtc_boot_time();
97 if (realTimeOffset == cpuData.real_time_offset)
98 return;
99
100 // The real time offset has changed. We need to update all affected
101 // timers. First find and dequeue them.
102 bigtime_t timeDiff = cpuData.real_time_offset - realTimeOffset;
103 cpuData.real_time_offset = realTimeOffset;
104
105 timer* affectedTimers = NULL;
106 timer** it = &cpuData.events;
107 timer* firstEvent = *it;
108 while (timer* event = *it) {
109 // check whether it's an absolute real-time timer
110 uint32 flags = event->flags;
111 if ((flags & ~B_TIMER_FLAGS) != B_ONE_SHOT_ABSOLUTE_TIMER
112 || (flags & B_TIMER_REAL_TIME_BASE) == 0) {
113 it = &event->next;
114 continue;
115 }
116
117 // Yep, remove the timer from the queue and add it to the
118 // affectedTimers list.
119 *it = event->next;
120 event->next = affectedTimers;
121 affectedTimers = event;
122 }
123
124 // update and requeue the affected timers
125 bool firstEventChanged = cpuData.events != firstEvent;
126 firstEvent = cpuData.events;
127
128 while (affectedTimers != NULL) {
129 timer* event = affectedTimers;
130 affectedTimers = event->next;
131
132 bigtime_t oldTime = event->schedule_time;
133 event->schedule_time += timeDiff;
134
135 // handle over-/underflows
136 if (timeDiff >= 0) {
137 if (event->schedule_time < oldTime)
138 event->schedule_time = B_INFINITE_TIMEOUT;
139 } else {
140 if (event->schedule_time < 0)
141 event->schedule_time = 0;
142 }
143
144 add_event_to_list(event, &cpuData.events);
145 }
146
147 firstEventChanged |= cpuData.events != firstEvent;
148
149 // If the first event has changed, reset the hardware timer.
150 if (firstEventChanged)
151 set_hardware_timer(cpuData.events->schedule_time);
152 }
153
154
155 // #pragma mark - debugging
156
157
158 static int
dump_timers(int argc,char ** argv)159 dump_timers(int argc, char** argv)
160 {
161 int32 cpuCount = smp_get_num_cpus();
162 for (int32 i = 0; i < cpuCount; i++) {
163 kprintf("CPU %" B_PRId32 ":\n", i);
164
165 if (sPerCPU[i].events == NULL) {
166 kprintf(" no timers scheduled\n");
167 continue;
168 }
169
170 for (timer* event = sPerCPU[i].events; event != NULL;
171 event = event->next) {
172 kprintf(" [%9lld] %p: ", (long long)event->schedule_time, event);
173 if ((event->flags & ~B_TIMER_FLAGS) == B_PERIODIC_TIMER)
174 kprintf("periodic %9lld, ", (long long)event->period);
175 else
176 kprintf("one shot, ");
177
178 kprintf("flags: %#x, user data: %p, callback: %p ",
179 event->flags, event->user_data, event->hook);
180
181 // look up and print the hook function symbol
182 const char* symbol;
183 const char* imageName;
184 bool exactMatch;
185
186 status_t error = elf_debug_lookup_symbol_address(
187 (addr_t)event->hook, NULL, &symbol, &imageName, &exactMatch);
188 if (error == B_OK && exactMatch) {
189 if (const char* slash = strchr(imageName, '/'))
190 imageName = slash + 1;
191
192 kprintf(" %s:%s", imageName, symbol);
193 }
194
195 kprintf("\n");
196 }
197 }
198
199 kprintf("current time: %lld\n", (long long)system_time());
200
201 return 0;
202 }
203
204
205 // #pragma mark - kernel-private
206
207
208 status_t
timer_init(kernel_args * args)209 timer_init(kernel_args* args)
210 {
211 TRACE(("timer_init: entry\n"));
212
213 if (arch_init_timer(args) != B_OK)
214 panic("arch_init_timer() failed");
215
216 add_debugger_command_etc("timers", &dump_timers, "List all timers",
217 "\n"
218 "Prints a list of all scheduled timers.\n", 0);
219
220 return B_OK;
221 }
222
223
224 void
timer_init_post_rtc(void)225 timer_init_post_rtc(void)
226 {
227 bigtime_t realTimeOffset = rtc_boot_time();
228
229 int32 cpuCount = smp_get_num_cpus();
230 for (int32 i = 0; i < cpuCount; i++)
231 sPerCPU[i].real_time_offset = realTimeOffset;
232 }
233
234
235 void
timer_real_time_clock_changed()236 timer_real_time_clock_changed()
237 {
238 call_all_cpus(&per_cpu_real_time_clock_changed, NULL);
239 }
240
241
242 int32
timer_interrupt()243 timer_interrupt()
244 {
245 per_cpu_timer_data& cpuData = sPerCPU[smp_get_current_cpu()];
246 int32 rc = B_HANDLED_INTERRUPT;
247
248 TRACE(("timer_interrupt: time %" B_PRIdBIGTIME ", cpu %" B_PRId32 "\n",
249 system_time(), smp_get_current_cpu()));
250
251 spinlock* spinlock = &cpuData.lock;
252 acquire_spinlock(spinlock);
253
254 timer* event = cpuData.events;
255 while (event != NULL && ((bigtime_t)event->schedule_time < system_time())) {
256 // this event needs to happen
257 int mode = event->flags;
258
259 cpuData.events = event->next;
260 cpuData.current_event = event;
261 atomic_set(&cpuData.current_event_in_progress, 1);
262
263 release_spinlock(spinlock);
264
265 TRACE(("timer_interrupt: calling hook %p for event %p\n", event->hook,
266 event));
267
268 // call the callback
269 // note: if the event is not periodic, it is ok
270 // to delete the event structure inside the callback
271 if (event->hook)
272 rc = event->hook(event);
273
274 atomic_set(&cpuData.current_event_in_progress, 0);
275
276 acquire_spinlock(spinlock);
277
278 if ((mode & ~B_TIMER_FLAGS) == B_PERIODIC_TIMER
279 && cpuData.current_event != NULL) {
280 // we need to adjust it and add it back to the list
281 event->schedule_time += event->period;
282
283 // If the new schedule time is a full interval or more in the past,
284 // skip ticks.
285 bigtime_t now = system_time();
286 if (now >= event->schedule_time + event->period) {
287 // pick the closest tick in the past
288 event->schedule_time = now
289 - (now - event->schedule_time) % event->period;
290 }
291
292 add_event_to_list(event, &cpuData.events);
293 }
294
295 cpuData.current_event = NULL;
296 event = cpuData.events;
297 }
298
299 // setup the next hardware timer
300 if (cpuData.events != NULL)
301 set_hardware_timer(cpuData.events->schedule_time);
302
303 release_spinlock(spinlock);
304
305 return rc;
306 }
307
308
309 // #pragma mark - public API
310
311
312 status_t
add_timer(timer * event,timer_hook hook,bigtime_t period,int32 flags)313 add_timer(timer* event, timer_hook hook, bigtime_t period, int32 flags)
314 {
315 const bigtime_t currentTime = system_time();
316
317 if (event == NULL || hook == NULL || period < 0)
318 return B_BAD_VALUE;
319
320 TRACE(("add_timer: event %p\n", event));
321
322 // compute the schedule time
323 if ((flags & B_TIMER_USE_TIMER_STRUCT_TIMES) == 0) {
324 bigtime_t scheduleTime = period;
325 if ((flags & ~B_TIMER_FLAGS) != B_ONE_SHOT_ABSOLUTE_TIMER)
326 scheduleTime += currentTime;
327 event->schedule_time = (int64)scheduleTime;
328 event->period = period;
329 }
330
331 event->hook = hook;
332 event->flags = flags;
333
334 InterruptsLocker interruptsLocker;
335 const int currentCPU = smp_get_current_cpu();
336 per_cpu_timer_data& cpuData = sPerCPU[currentCPU];
337 SpinLocker locker(&cpuData.lock);
338
339 // If the timer is an absolute real-time base timer, convert the schedule
340 // time to system time.
341 if ((flags & ~B_TIMER_FLAGS) == B_ONE_SHOT_ABSOLUTE_TIMER
342 && (flags & B_TIMER_REAL_TIME_BASE) != 0) {
343 if (event->schedule_time > cpuData.real_time_offset)
344 event->schedule_time -= cpuData.real_time_offset;
345 else
346 event->schedule_time = 0;
347 }
348
349 add_event_to_list(event, &cpuData.events);
350 event->cpu = currentCPU;
351
352 // if we were stuck at the head of the list, set the hardware timer
353 if (event == cpuData.events)
354 set_hardware_timer(event->schedule_time, currentTime);
355
356 return B_OK;
357 }
358
359
360 bool
cancel_timer(timer * event)361 cancel_timer(timer* event)
362 {
363 TRACE(("cancel_timer: event %p\n", event));
364
365 InterruptsLocker _;
366
367 // lock the right CPU spinlock
368 int cpu = event->cpu;
369 SpinLocker spinLocker;
370 while (true) {
371 if (cpu >= SMP_MAX_CPUS)
372 return false;
373
374 spinLocker.SetTo(sPerCPU[cpu].lock, false);
375 if (cpu == event->cpu)
376 break;
377
378 // cpu field changed while we were trying to lock
379 spinLocker.Unlock();
380 cpu = event->cpu;
381 }
382
383 per_cpu_timer_data& cpuData = sPerCPU[cpu];
384
385 if (event != cpuData.current_event) {
386 // The timer hook is not yet being executed.
387 timer* current = cpuData.events;
388 timer* previous = NULL;
389
390 while (current != NULL) {
391 if (current == event) {
392 // we found it
393 if (previous == NULL)
394 cpuData.events = current->next;
395 else
396 previous->next = current->next;
397 current->next = NULL;
398 // break out of the whole thing
399 break;
400 }
401 previous = current;
402 current = current->next;
403 }
404
405 // If not found, we assume this was a one-shot timer and has already
406 // fired.
407 if (current == NULL)
408 return true;
409
410 // invalidate CPU field
411 event->cpu = 0xffff;
412
413 // If on the current CPU, also reset the hardware timer.
414 // FIXME: Theoretically we should be able to skip this if (previous == NULL).
415 // But it seems adding that causes problems on some systems, possibly due to
416 // some other bug. For now, just reset the hardware timer on every cancellation.
417 if (cpu == smp_get_current_cpu()) {
418 if (cpuData.events == NULL)
419 arch_timer_clear_hardware_timer();
420 else
421 set_hardware_timer(cpuData.events->schedule_time);
422 }
423
424 return false;
425 }
426
427 // The timer hook is currently being executed. We clear the current
428 // event so that timer_interrupt() will not reschedule periodic timers.
429 cpuData.current_event = NULL;
430
431 // Unless this is a kernel-private timer that also requires the scheduler
432 // lock to be held while calling the event hook, we'll have to wait
433 // for the hook to complete. When called from the timer hook we don't
434 // wait either, of course.
435 if (cpu != smp_get_current_cpu()) {
436 spinLocker.Unlock();
437
438 while (atomic_get(&cpuData.current_event_in_progress) == 1)
439 cpu_wait(&cpuData.current_event_in_progress, 0);
440 }
441
442 return true;
443 }
444
445
446 void
spin(bigtime_t microseconds)447 spin(bigtime_t microseconds)
448 {
449 bigtime_t time = system_time();
450
451 while ((system_time() - time) < microseconds)
452 cpu_pause();
453 }
454