ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/cebix/SheepShaver/src/timer.cpp
Revision: 1.4
Committed: 2005-03-05T19:07:35Z (19 years, 2 months ago) by gbeauche
Branch: MAIN
Changes since 1.3: +195 -17 lines
Log Message:
Enable high precision timings on POSIX systems supporting clock_nanosleep().
Since pthread_suspend_np() is not available to Linux (but NetBSD 2.0), thread
suspend is implemented likewise to boehm-gc.

File Contents

# Content
1 /*
2 * timer.cpp - Time Manager emulation
3 *
4 * SheepShaver (C) 1997-2005 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 /*
22 * TODO: Prime(0)
23 */
24
25 #include "sysdeps.h"
26 #include "timer.h"
27 #include "macos_util.h"
28 #include "main.h"
29 #include "cpu_emulation.h"
30
31 #ifdef PRECISE_TIMING_POSIX
32 #include <pthread.h>
33 #include <semaphore.h>
34 #endif
35
36 #define DEBUG 0
37 #include "debug.h"
38
39
40 #define TM_QUEUE 0 // Enable TMQueue management (doesn't work)
41
42
43 // Definitions for Time Manager
44 enum { // TMTask struct
45 tmAddr = 6,
46 tmCount = 10,
47 tmWakeUp = 14,
48 tmReserved = 18
49 };
50
51
52 // Array of additional info for each installed TMTask
53 struct TMDesc {
54 uint32 task; // Mac address of associated TMTask
55 tm_time_t wakeup; // Time this task is scheduled for execution
56 bool in_use; // Flag: descriptor in use
57 };
58
59 const int NUM_DESCS = 64; // Maximum number of descriptors
60 static TMDesc desc[NUM_DESCS];
61
62 #if PRECISE_TIMING
63 #ifdef PRECISE_TIMING_BEOS
64 static thread_id timer_thread = -1;
65 static bool thread_active = true;
66 static const tm_time_t wakeup_time_max = 0x7fffffffffffffff;
67 static volatile tm_time_t wakeup_time = wakeup_time_max;
68 static sem_id wakeup_time_sem = -1;
69 static int32 timer_func(void *arg);
70 #endif
71 #ifdef PRECISE_TIMING_POSIX
72 static pthread_t timer_thread;
73 static volatile bool thread_active = false;
74 static tm_time_t wakeup_time_max = { 0x7fffffff, 999999999 };
75 static tm_time_t wakeup_time = wakeup_time_max;
76 static sem_t wakeup_time_sem;
77 static void *timer_func(void *arg);
78 #endif
79 #endif
80
81
82 /*
83 * Allocate descriptor for given TMTask in list
84 */
85
86 static int alloc_desc(uint32 tm)
87 {
88 // Search for first free descriptor
89 for (int i=0; i<NUM_DESCS; i++)
90 if (!desc[i].in_use) {
91 desc[i].task = tm;
92 desc[i].in_use = true;
93 return i;
94 }
95 return -1;
96 }
97
98
99 /*
100 * Free descriptor in list
101 */
102
103 inline static void free_desc(int i)
104 {
105 desc[i].in_use = false;
106 }
107
108
109 /*
110 * Find descriptor associated with given TMTask
111 */
112
113 inline static int find_desc(uint32 tm)
114 {
115 for (int i=0; i<NUM_DESCS; i++)
116 if (desc[i].in_use && desc[i].task == tm)
117 return i;
118 return -1;
119 }
120
121
122 /*
123 * Enqueue task in Time Manager queue
124 */
125
126 static void enqueue_tm(uint32 tm)
127 {
128 #if TM_QUEUE
129 uint32 tm_var = ReadMacInt32(0xb30);
130 WriteMacInt32(tm + qLink, ReadMacInt32(tm_var));
131 WriteMacInt32(tm_var, tm);
132 #endif
133 }
134
135
136 /*
137 * Remove task from Time Manager queue
138 */
139
140 static void dequeue_tm(uint32 tm)
141 {
142 #if TM_QUEUE
143 uint32 p = ReadMacInt32(0xb30);
144 while (p) {
145 uint32 next = ReadMacInt32(p + qLink);
146 if (next == tm) {
147 WriteMacInt32(p + qLink, ReadMacInt32(next + qLink));
148 return;
149 }
150 }
151 #endif
152 }
153
154
155 /*
156 * Timer thread operations
157 */
158
159 #ifdef PRECISE_TIMING_POSIX
160 const int SIGSUSPEND = SIGRTMIN + 6;
161 const int SIGRESUME = SIGRTMIN + 7;
162 static struct sigaction sigsuspend_action;
163 static struct sigaction sigresume_action;
164
165 static int suspend_count = 0;
166 static pthread_mutex_t suspend_count_lock = PTHREAD_MUTEX_INITIALIZER;
167 static sem_t suspend_ack_sem;
168
169 // Signal handler for suspended thread
170 static void sigsuspend_handler(int sig)
171 {
172 sem_post(&suspend_ack_sem);
173
174 sigset_t mask;
175 sigfillset(&mask);
176 sigdelset(&mask, SIGRESUME);
177 sigsuspend(&mask);
178 }
179
180 // Signal handler for resumed thread
181 static void sigresume_handler(int sig)
182 {
183 /* simply trigger a signal to stop clock_nanosleep() */
184 }
185
186 // Initialize timer thread
187 static bool timer_thread_init(void)
188 {
189 // Install suspend signal handler
190 sigfillset(&sigsuspend_action.sa_mask);
191 sigsuspend_action.sa_handler = sigsuspend_handler;
192 sigsuspend_action.sa_flags = SA_RESTART;
193 #ifdef HAVE_SIGNAL_SA_RESTORER
194 sigsuspend_action.sa_restorer = NULL;
195 #endif
196 if (sigaction(SIGSUSPEND, &sigsuspend_action, NULL) < 0)
197 return false;
198
199 // Install resume signal handler
200 sigfillset(&sigresume_action.sa_mask);
201 sigresume_action.sa_handler = sigresume_handler;
202 sigresume_action.sa_flags = SA_RESTART;
203 #ifdef HAVE_SIGNAL_SA_RESTORER
204 sigresume_action.sa_restorer = NULL;
205 #endif
206 if (sigaction(SIGRESUME, &sigresume_action, NULL) < 0)
207 return false;
208
209 // Initialize semaphore
210 if (sem_init(&suspend_ack_sem, 0, 0) < 0)
211 return false;
212
213 // Create thread in running state
214 suspend_count = 0;
215 return (pthread_create(&timer_thread, NULL, timer_func, NULL) == 0);
216 }
217
218 // Kill timer thread
219 static void timer_thread_kill(void)
220 {
221 #ifdef HAVE_PTHREAD_CANCEL
222 pthread_cancel(timer_thread);
223 #endif
224 pthread_join(timer_thread, NULL);
225 }
226
227 // Suspend timer thread
228 static void timer_thread_suspend(void)
229 {
230 pthread_mutex_lock(&suspend_count_lock);
231 if (suspend_count == 0) {
232 suspend_count ++;
233 if (pthread_kill(timer_thread, SIGSUSPEND) == 0)
234 sem_wait(&suspend_ack_sem);
235 }
236 pthread_mutex_unlock(&suspend_count_lock);
237 }
238
239 // Resume timer thread
240 static void timer_thread_resume(void)
241 {
242 pthread_mutex_lock(&suspend_count_lock);
243 assert(suspend_count > 0);
244 if (suspend_count == 1) {
245 suspend_count = 0;
246 pthread_kill(timer_thread, SIGRESUME);
247 }
248 pthread_mutex_unlock(&suspend_count_lock);
249 }
250 #endif
251
252
253 /*
254 * Initialize Time Manager
255 */
256
257 void TimerInit(void)
258 {
259 // Mark all descriptors as inactive
260 for (int i=0; i<NUM_DESCS; i++)
261 free_desc(i);
262
263 #if PRECISE_TIMING
264 // Start timer thread
265 #ifdef PRECISE_TIMING_BEOS
266 wakeup_time_sem = create_sem(1, "Wakeup Time");
267 timer_thread = spawn_thread(timer_func, "Time Manager", B_REAL_TIME_PRIORITY, NULL);
268 resume_thread(timer_thread);
269 #endif
270 #ifdef PRECISE_TIMING_POSIX
271 sem_init(&wakeup_time_sem, 0, 1);
272 thread_active = timer_thread_init();
273 #endif
274 #endif
275 }
276
277
278 /*
279 * Exit Time Manager
280 */
281
282 void TimerExit(void)
283 {
284 #if PRECISE_TIMING
285 // Quit timer thread
286 if (timer_thread > 0) {
287 #ifdef PRECISE_TIMING_BEOS
288 status_t l;
289 thread_active = false;
290 suspend_thread(timer_thread);
291 resume_thread(timer_thread);
292 wait_for_thread(timer_thread, &l);
293 delete_sem(wakeup_time_sem);
294 #endif
295 #ifdef PRECISE_TIMING_POSIX
296 thread_active = false;
297 timer_thread_kill();
298 sem_destroy(&wakeup_time_sem);
299 #endif
300 }
301 #endif
302 }
303
304
305 /*
306 * Emulator reset, remove all timer tasks
307 */
308
309 void TimerReset(void)
310 {
311 // Mark all descriptors as inactive
312 for (int i=0; i<NUM_DESCS; i++)
313 free_desc(i);
314 }
315
316
317 /*
318 * Insert timer task
319 */
320
321 int16 InsTime(uint32 tm, uint16 trap)
322 {
323 D(bug("InsTime %08lx, trap %04x\n", tm, trap));
324 WriteMacInt16((uint32)tm + qType, ReadMacInt16((uint32)tm + qType) & 0x1fff | (trap << 4) & 0x6000);
325 if (find_desc(tm) >= 0)
326 printf("WARNING: InsTime(): Task re-inserted\n");
327 else {
328 int i = alloc_desc(tm);
329 if (i < 0)
330 printf("FATAL: InsTime(): No free Time Manager descriptor\n");
331 }
332 return 0;
333 }
334
335
336 /*
337 * Remove timer task
338 */
339
340 int16 RmvTime(uint32 tm)
341 {
342 D(bug("RmvTime %08lx\n", tm));
343
344 // Find descriptor
345 int i = find_desc(tm);
346 if (i < 0) {
347 printf("WARNING: RmvTime(%08lx): Descriptor not found\n", tm);
348 return 0;
349 }
350
351 // Task active?
352 #if PRECISE_TIMING_BEOS
353 while (acquire_sem(wakeup_time_sem) == B_INTERRUPTED) ;
354 suspend_thread(timer_thread);
355 #endif
356 #if PRECISE_TIMING_POSIX
357 sem_wait(&wakeup_time_sem);
358 timer_thread_suspend();
359 #endif
360 if (ReadMacInt16(tm + qType) & 0x8000) {
361
362 // Yes, make task inactive and remove it from the Time Manager queue
363 WriteMacInt16(tm + qType, ReadMacInt16(tm + qType) & 0x7fff);
364 dequeue_tm(tm);
365 #if PRECISE_TIMING
366 // Look for next task to be called and set wakeup_time
367 wakeup_time = wakeup_time_max;
368 for (int j=0; j<NUM_DESCS; j++) {
369 if (desc[j].in_use && (ReadMacInt16(desc[j].task + qType) & 0x8000))
370 if (timer_cmp_time(desc[j].wakeup, wakeup_time) < 0)
371 wakeup_time = desc[j].wakeup;
372 }
373 #endif
374
375 // Compute remaining time
376 tm_time_t remaining, current;
377 timer_current_time(current);
378 timer_sub_time(remaining, desc[i].wakeup, current);
379 WriteMacInt32(tm + tmCount, timer_host2mac_time(remaining));
380 } else
381 WriteMacInt32(tm + tmCount, 0);
382 D(bug(" tmCount %ld\n", ReadMacInt32(tm + tmCount)));
383 #if PRECISE_TIMING_BEOS
384 release_sem(wakeup_time_sem);
385 thread_info info;
386 do {
387 resume_thread(timer_thread); // This will unblock the thread
388 get_thread_info(timer_thread, &info);
389 } while (info.state == B_THREAD_SUSPENDED); // Sometimes, resume_thread() doesn't work (BeOS bug?)
390 #endif
391 #if PRECISE_TIMING_POSIX
392 sem_post(&wakeup_time_sem);
393 timer_thread_resume();
394 assert(suspend_count == 0);
395 #endif
396
397 // Free descriptor
398 free_desc(i);
399 return 0;
400 }
401
402
403 /*
404 * Start timer task
405 */
406
407 int16 PrimeTime(uint32 tm, int32 time)
408 {
409 D(bug("PrimeTime %08lx, time %ld\n", tm, time));
410
411 // Find descriptor
412 int i = find_desc(tm);
413 if (i < 0) {
414 printf("FATAL: PrimeTime(): Descriptor not found\n");
415 return 0;
416 }
417
418 // Convert delay time
419 tm_time_t delay;
420 timer_mac2host_time(delay, time);
421
422 // Extended task?
423 if (ReadMacInt16(tm + qType) & 0x4000) {
424
425 // Yes, tmWakeUp set?
426 if (ReadMacInt32(tm + tmWakeUp)) {
427
428 //!! PrimeTime(0) means continue previous delay
429 // (save wakeup time in RmvTime?)
430 if (time == 0) {
431 printf("FATAL: Unsupported PrimeTime(0)\n");
432 return 0;
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 sem_wait(&wakeup_time_sem);
466 timer_thread_suspend();
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 sem_post(&wakeup_time_sem);
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 (thread_active) {
526
527 // Wait until time specified by wakeup_time
528 clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &wakeup_time, NULL);
529
530 sem_wait(&wakeup_time_sem);
531 tm_time_t system_time;
532 timer_current_time(system_time);
533 if (timer_cmp_time(wakeup_time, system_time) < 0) {
534
535 // Timer expired, trigger interrupt
536 wakeup_time = wakeup_time_max;
537 SetInterruptFlag(INTFLAG_TIMER);
538 TriggerInterrupt();
539 }
540 sem_post(&wakeup_time_sem);
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 sem_wait(&wakeup_time_sem);
588 timer_thread_suspend();
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 sem_post(&wakeup_time_sem);
606 timer_thread_resume();
607 assert(suspend_count == 0);
608 #endif
609 #endif
610 }