ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/cebix/SheepShaver/src/timer.cpp
Revision: 1.11
Committed: 2009-08-01T07:02:41Z (14 years, 9 months ago) by asvitkine
Branch: MAIN
Changes since 1.10: +3 -1 lines
Log Message:
only "continue previous delay" if delay is 0

File Contents

# Content
1 /*
2 * timer.cpp - Time Manager emulation
3 *
4 * SheepShaver (C) 1997-2008 Christian Bauer and Marc Hellwig
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20
21 #include "sysdeps.h"
22 #include "timer.h"
23 #include "macos_util.h"
24 #include "main.h"
25 #include "cpu_emulation.h"
26
27 #ifdef PRECISE_TIMING_POSIX
28 #include <pthread.h>
29 #include <semaphore.h>
30 #endif
31
32 #define DEBUG 0
33 #include "debug.h"
34
35
36 #define TM_QUEUE 0 // Enable TMQueue management (doesn't work)
37
38
39 // Definitions for Time Manager
40 enum { // TMTask struct
41 tmAddr = 6,
42 tmCount = 10,
43 tmWakeUp = 14,
44 tmReserved = 18
45 };
46
47
48 // Array of additional info for each installed TMTask
49 struct TMDesc {
50 uint32 task; // Mac address of associated TMTask
51 tm_time_t wakeup; // Time this task is scheduled for execution
52 bool in_use; // Flag: descriptor in use
53 };
54
55 const int NUM_DESCS = 64; // Maximum number of descriptors
56 static TMDesc desc[NUM_DESCS];
57
58 #if PRECISE_TIMING
59 #ifdef PRECISE_TIMING_BEOS
60 static thread_id timer_thread = -1;
61 static bool thread_active = true;
62 static const tm_time_t wakeup_time_max = 0x7fffffffffffffff;
63 static volatile tm_time_t wakeup_time = wakeup_time_max;
64 static sem_id wakeup_time_sem = -1;
65 static int32 timer_func(void *arg);
66 #endif
67 #ifdef PRECISE_TIMING_POSIX
68 static pthread_t timer_thread;
69 static bool timer_thread_active = false;
70 static volatile bool timer_thread_cancel = false;
71 static tm_time_t wakeup_time_max = { 0x7fffffff, 999999999 };
72 static tm_time_t wakeup_time = wakeup_time_max;
73 static pthread_mutex_t wakeup_time_lock = PTHREAD_MUTEX_INITIALIZER;
74 static void *timer_func(void *arg);
75 #endif
76 #endif
77
78
79 /*
80 * Allocate descriptor for given TMTask in list
81 */
82
83 static int alloc_desc(uint32 tm)
84 {
85 // Search for first free descriptor
86 for (int i=0; i<NUM_DESCS; i++)
87 if (!desc[i].in_use) {
88 desc[i].task = tm;
89 desc[i].in_use = true;
90 return i;
91 }
92 return -1;
93 }
94
95
96 /*
97 * Free descriptor in list
98 */
99
100 inline static void free_desc(int i)
101 {
102 desc[i].in_use = false;
103 }
104
105
106 /*
107 * Find descriptor associated with given TMTask
108 */
109
110 inline static int find_desc(uint32 tm)
111 {
112 for (int i=0; i<NUM_DESCS; i++)
113 if (desc[i].in_use && desc[i].task == tm)
114 return i;
115 return -1;
116 }
117
118
119 /*
120 * Enqueue task in Time Manager queue
121 */
122
123 static void enqueue_tm(uint32 tm)
124 {
125 #if TM_QUEUE
126 uint32 tm_var = ReadMacInt32(0xb30);
127 WriteMacInt32(tm + qLink, ReadMacInt32(tm_var));
128 WriteMacInt32(tm_var, tm);
129 #endif
130 }
131
132
133 /*
134 * Remove task from Time Manager queue
135 */
136
137 static void dequeue_tm(uint32 tm)
138 {
139 #if TM_QUEUE
140 uint32 p = ReadMacInt32(0xb30);
141 while (p) {
142 uint32 next = ReadMacInt32(p + qLink);
143 if (next == tm) {
144 WriteMacInt32(p + qLink, ReadMacInt32(next + qLink));
145 return;
146 }
147 }
148 #endif
149 }
150
151
152 /*
153 * Timer thread operations
154 */
155
156 #ifdef PRECISE_TIMING_POSIX
157 const int SIGSUSPEND = SIGRTMIN + 6;
158 const int SIGRESUME = SIGRTMIN + 7;
159 static struct sigaction sigsuspend_action;
160 static struct sigaction sigresume_action;
161
162 static int suspend_count = 0;
163 static pthread_mutex_t suspend_count_lock = PTHREAD_MUTEX_INITIALIZER;
164 static sem_t suspend_ack_sem;
165 static sigset_t suspend_handler_mask;
166
167 // Signal handler for suspended thread
168 static void sigsuspend_handler(int sig)
169 {
170 sem_post(&suspend_ack_sem);
171 sigsuspend(&suspend_handler_mask);
172 }
173
174 // Signal handler for resumed thread
175 static void sigresume_handler(int sig)
176 {
177 /* simply trigger a signal to stop clock_nanosleep() */
178 }
179
180 // Initialize timer thread
181 static bool timer_thread_init(void)
182 {
183 // Install suspend signal handler
184 sigemptyset(&sigsuspend_action.sa_mask);
185 sigaddset(&sigsuspend_action.sa_mask, SIGRESUME);
186 sigsuspend_action.sa_handler = sigsuspend_handler;
187 sigsuspend_action.sa_flags = SA_RESTART;
188 #ifdef HAVE_SIGNAL_SA_RESTORER
189 sigsuspend_action.sa_restorer = NULL;
190 #endif
191 if (sigaction(SIGSUSPEND, &sigsuspend_action, NULL) < 0)
192 return false;
193
194 // Install resume signal handler
195 sigemptyset(&sigresume_action.sa_mask);
196 sigresume_action.sa_handler = sigresume_handler;
197 sigresume_action.sa_flags = SA_RESTART;
198 #ifdef HAVE_SIGNAL_SA_RESTORER
199 sigresume_action.sa_restorer = NULL;
200 #endif
201 if (sigaction(SIGRESUME, &sigresume_action, NULL) < 0)
202 return false;
203
204 // Initialize semaphore
205 if (sem_init(&suspend_ack_sem, 0, 0) < 0)
206 return false;
207
208 // Initialize suspend_handler_mask, it excludes SIGRESUME
209 if (sigfillset(&suspend_handler_mask) != 0)
210 return false;
211 if (sigdelset(&suspend_handler_mask, SIGRESUME) != 0)
212 return false;
213
214 // Create thread in running state
215 suspend_count = 0;
216 return (pthread_create(&timer_thread, NULL, timer_func, NULL) == 0);
217 }
218
219 // Kill timer thread
220 static void timer_thread_kill(void)
221 {
222 timer_thread_cancel = true;
223 #ifdef HAVE_PTHREAD_CANCEL
224 pthread_cancel(timer_thread);
225 #endif
226 pthread_join(timer_thread, NULL);
227 }
228
229 // Suspend timer thread
230 static void timer_thread_suspend(void)
231 {
232 pthread_mutex_lock(&suspend_count_lock);
233 if (suspend_count == 0) {
234 suspend_count ++;
235 if (pthread_kill(timer_thread, SIGSUSPEND) == 0)
236 sem_wait(&suspend_ack_sem);
237 }
238 pthread_mutex_unlock(&suspend_count_lock);
239 }
240
241 // Resume timer thread
242 static void timer_thread_resume(void)
243 {
244 pthread_mutex_lock(&suspend_count_lock);
245 assert(suspend_count > 0);
246 if (suspend_count == 1) {
247 suspend_count = 0;
248 pthread_kill(timer_thread, SIGRESUME);
249 }
250 pthread_mutex_unlock(&suspend_count_lock);
251 }
252 #endif
253
254
255 /*
256 * Initialize Time Manager
257 */
258
259 void TimerInit(void)
260 {
261 // Mark all descriptors as inactive
262 for (int i=0; i<NUM_DESCS; i++)
263 free_desc(i);
264
265 #if PRECISE_TIMING
266 // Start timer thread
267 #ifdef PRECISE_TIMING_BEOS
268 wakeup_time_sem = create_sem(1, "Wakeup Time");
269 timer_thread = spawn_thread(timer_func, "Time Manager", B_REAL_TIME_PRIORITY, NULL);
270 resume_thread(timer_thread);
271 #endif
272 #ifdef PRECISE_TIMING_POSIX
273 timer_thread_active = timer_thread_init();
274 #endif
275 #endif
276 }
277
278
279 /*
280 * Exit Time Manager
281 */
282
283 void TimerExit(void)
284 {
285 #if PRECISE_TIMING
286 // Quit timer thread
287 if (timer_thread > 0) {
288 #ifdef PRECISE_TIMING_BEOS
289 status_t l;
290 thread_active = false;
291 suspend_thread(timer_thread);
292 resume_thread(timer_thread);
293 wait_for_thread(timer_thread, &l);
294 delete_sem(wakeup_time_sem);
295 #endif
296 #ifdef PRECISE_TIMING_POSIX
297 timer_thread_kill();
298 #endif
299 }
300 #endif
301 }
302
303
304 /*
305 * Emulator reset, remove all timer tasks
306 */
307
308 void TimerReset(void)
309 {
310 // Mark all descriptors as inactive
311 for (int i=0; i<NUM_DESCS; i++)
312 free_desc(i);
313 }
314
315
316 /*
317 * Insert timer task
318 */
319
320 int16 InsTime(uint32 tm, uint16 trap)
321 {
322 D(bug("InsTime %08lx, trap %04x\n", tm, trap));
323 WriteMacInt16((uint32)tm + qType, ReadMacInt16((uint32)tm + qType) & 0x1fff | (trap << 4) & 0x6000);
324 if (find_desc(tm) >= 0)
325 printf("WARNING: InsTime(): Task re-inserted\n");
326 else {
327 int i = alloc_desc(tm);
328 if (i < 0)
329 printf("FATAL: InsTime(): No free Time Manager descriptor\n");
330 }
331 return 0;
332 }
333
334
335 /*
336 * Remove timer task
337 */
338
339 int16 RmvTime(uint32 tm)
340 {
341 D(bug("RmvTime %08lx\n", tm));
342
343 // Find descriptor
344 int i = find_desc(tm);
345 if (i < 0) {
346 printf("WARNING: RmvTime(%08lx): Descriptor not found\n", tm);
347 return 0;
348 }
349
350 // Task active?
351 #if PRECISE_TIMING_BEOS
352 while (acquire_sem(wakeup_time_sem) == B_INTERRUPTED) ;
353 suspend_thread(timer_thread);
354 #endif
355 #if PRECISE_TIMING_POSIX
356 timer_thread_suspend();
357 pthread_mutex_lock(&wakeup_time_lock);
358 #endif
359 if (ReadMacInt16(tm + qType) & 0x8000) {
360
361 // Yes, make task inactive and remove it from the Time Manager queue
362 WriteMacInt16(tm + qType, ReadMacInt16(tm + qType) & 0x7fff);
363 dequeue_tm(tm);
364 #if PRECISE_TIMING
365 // Look for next task to be called and set wakeup_time
366 wakeup_time = wakeup_time_max;
367 for (int j=0; j<NUM_DESCS; j++) {
368 if (desc[j].in_use && (ReadMacInt16(desc[j].task + qType) & 0x8000))
369 if (timer_cmp_time(desc[j].wakeup, wakeup_time) < 0)
370 wakeup_time = desc[j].wakeup;
371 }
372 #endif
373
374 // Compute remaining time
375 tm_time_t remaining, current;
376 timer_current_time(current);
377 timer_sub_time(remaining, desc[i].wakeup, current);
378 WriteMacInt32(tm + tmCount, timer_host2mac_time(remaining));
379 } else
380 WriteMacInt32(tm + tmCount, 0);
381 D(bug(" tmCount %ld\n", ReadMacInt32(tm + tmCount)));
382 #if PRECISE_TIMING_BEOS
383 release_sem(wakeup_time_sem);
384 thread_info info;
385 do {
386 resume_thread(timer_thread); // This will unblock the thread
387 get_thread_info(timer_thread, &info);
388 } while (info.state == B_THREAD_SUSPENDED); // Sometimes, resume_thread() doesn't work (BeOS bug?)
389 #endif
390 #if PRECISE_TIMING_POSIX
391 pthread_mutex_unlock(&wakeup_time_lock);
392 timer_thread_resume();
393 assert(suspend_count == 0);
394 #endif
395
396 // Free descriptor
397 free_desc(i);
398 return 0;
399 }
400
401
402 /*
403 * Start timer task
404 */
405
406 int16 PrimeTime(uint32 tm, int32 time)
407 {
408 D(bug("PrimeTime %08lx, time %ld\n", tm, time));
409
410 // Find descriptor
411 int i = find_desc(tm);
412 if (i < 0) {
413 printf("FATAL: PrimeTime(): Descriptor not found\n");
414 return 0;
415 }
416
417 // Convert delay time
418 tm_time_t delay;
419 timer_mac2host_time(delay, time);
420
421 // Extended task?
422 if (ReadMacInt16(tm + qType) & 0x4000) {
423
424 // Yes, tmWakeUp set?
425 if (ReadMacInt32(tm + tmWakeUp)) {
426
427 // PrimeTime(0) can either mean (a) "the task runs as soon as interrupts are enabled"
428 // or (b) "continue previous delay" if an expired task was stopped via RmvTime() and
429 // then re-installed using InsXTime(). Since tmWakeUp was set, this is case (b).
430 // The remaining time was saved in tmCount by RmvTime().
431 if (time == 0) {
432 timer_mac2host_time(delay, ReadMacInt16(tm + tmCount));
433 }
434
435 // Yes, calculate wakeup time relative to last scheduled time
436 tm_time_t wakeup;
437 timer_add_time(wakeup, desc[i].wakeup, delay);
438 desc[i].wakeup = wakeup;
439
440 } else {
441
442 // No, calculate wakeup time relative to current time
443 tm_time_t now;
444 timer_current_time(now);
445 timer_add_time(desc[i].wakeup, now, delay);
446 }
447
448 // Set tmWakeUp to indicate that task was scheduled
449 WriteMacInt32(tm + tmWakeUp, 0x12345678);
450
451 } else {
452
453 // Not extended task, calculate wakeup time relative to current time
454 tm_time_t now;
455 timer_current_time(now);
456 timer_add_time(desc[i].wakeup, now, delay);
457 }
458
459 // Make task active and enqueue it in the Time Manager queue
460 #if PRECISE_TIMING_BEOS
461 while (acquire_sem(wakeup_time_sem) == B_INTERRUPTED) ;
462 suspend_thread(timer_thread);
463 #endif
464 #if PRECISE_TIMING_POSIX
465 timer_thread_suspend();
466 pthread_mutex_lock(&wakeup_time_lock);
467 #endif
468 WriteMacInt16(tm + qType, ReadMacInt16(tm + qType) | 0x8000);
469 enqueue_tm(tm);
470 #if PRECISE_TIMING
471 // Look for next task to be called and set wakeup_time
472 wakeup_time = wakeup_time_max;
473 for (int j=0; j<NUM_DESCS; j++) {
474 if (desc[j].in_use && (ReadMacInt16(desc[j].task + qType) & 0x8000))
475 if (timer_cmp_time(desc[j].wakeup, wakeup_time) < 0)
476 wakeup_time = desc[j].wakeup;
477 }
478 #ifdef PRECISE_TIMING_BEOS
479 release_sem(wakeup_time_sem);
480 thread_info info;
481 do {
482 resume_thread(timer_thread); // This will unblock the thread
483 get_thread_info(timer_thread, &info);
484 } while (info.state == B_THREAD_SUSPENDED); // Sometimes, resume_thread() doesn't work (BeOS bug?)
485 #endif
486 #ifdef PRECISE_TIMING_POSIX
487 pthread_mutex_unlock(&wakeup_time_lock);
488 timer_thread_resume();
489 assert(suspend_count == 0);
490 #endif
491 #endif
492 return 0;
493 }
494
495
496 /*
497 * Time Manager thread
498 */
499
500 #ifdef PRECISE_TIMING_BEOS
501 static int32 timer_func(void *arg)
502 {
503 while (thread_active) {
504
505 // Wait until time specified by wakeup_time
506 snooze_until(wakeup_time, B_SYSTEM_TIMEBASE);
507
508 while (acquire_sem(wakeup_time_sem) == B_INTERRUPTED) ;
509 if (wakeup_time < system_time()) {
510
511 // Timer expired, trigger interrupt
512 wakeup_time = 0x7fffffffffffffff;
513 SetInterruptFlag(INTFLAG_TIMER);
514 TriggerInterrupt();
515 }
516 release_sem(wakeup_time_sem);
517 }
518 return 0;
519 }
520 #endif
521
522 #ifdef PRECISE_TIMING_POSIX
523 static void *timer_func(void *arg)
524 {
525 while (!timer_thread_cancel) {
526
527 // Wait until time specified by wakeup_time
528 clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &wakeup_time, NULL);
529
530 tm_time_t system_time;
531 timer_current_time(system_time);
532 if (timer_cmp_time(wakeup_time, system_time) < 0) {
533
534 // Timer expired, trigger interrupt
535 pthread_mutex_lock(&wakeup_time_lock);
536 wakeup_time = wakeup_time_max;
537 pthread_mutex_unlock(&wakeup_time_lock);
538 SetInterruptFlag(INTFLAG_TIMER);
539 TriggerInterrupt();
540 }
541 }
542 return NULL;
543 }
544 #endif
545
546
547 /*
548 * Timer interrupt function (executed as part of 60Hz interrupt)
549 */
550
551 void TimerInterrupt(void)
552 {
553 // D(bug("TimerIRQ\n"));
554
555 // Look for active TMTasks that have expired
556 tm_time_t now;
557 timer_current_time(now);
558 for (int i=0; i<NUM_DESCS; i++)
559 if (desc[i].in_use) {
560 uint32 tm = desc[i].task;
561 if ((ReadMacInt16(tm + qType) & 0x8000) && timer_cmp_time(desc[i].wakeup, now) <= 0) {
562
563 // Found one, mark as inactive and remove it from the Time Manager queue
564 WriteMacInt16(tm + qType, ReadMacInt16(tm + qType) & 0x7fff);
565 dequeue_tm(tm);
566
567 // Call timer function
568 uint32 addr = ReadMacInt32(tm + tmAddr);
569 if (addr) {
570 D(bug("Calling TimeTask %08lx, addr %08lx\n", tm, addr));
571 M68kRegisters r;
572 r.a[0] = addr;
573 r.a[1] = tm;
574 Execute68k(r.a[0], &r);
575 D(bug(" returned from TimeTask\n"));
576 }
577 }
578 }
579
580 #if PRECISE_TIMING
581 // Look for next task to be called and set wakeup_time
582 #if PRECISE_TIMING_BEOS
583 while (acquire_sem(wakeup_time_sem) == B_INTERRUPTED) ;
584 suspend_thread(timer_thread);
585 #endif
586 #if PRECISE_TIMING_POSIX
587 timer_thread_suspend();
588 pthread_mutex_lock(&wakeup_time_lock);
589 #endif
590 wakeup_time = wakeup_time_max;
591 for (int j=0; j<NUM_DESCS; j++) {
592 if (desc[j].in_use && (ReadMacInt16(desc[j].task + qType) & 0x8000))
593 if (timer_cmp_time(desc[j].wakeup, wakeup_time) < 0)
594 wakeup_time = desc[j].wakeup;
595 }
596 #if PRECISE_TIMING_BEOS
597 release_sem(wakeup_time_sem);
598 thread_info info;
599 do {
600 resume_thread(timer_thread); // This will unblock the thread
601 get_thread_info(timer_thread, &info);
602 } while (info.state == B_THREAD_SUSPENDED); // Sometimes, resume_thread() doesn't work (BeOS bug?)
603 #endif
604 #if PRECISE_TIMING_POSIX
605 pthread_mutex_unlock(&wakeup_time_lock);
606 timer_thread_resume();
607 assert(suspend_count == 0);
608 #endif
609 #endif
610 }