ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/cebix/SheepShaver/src/timer.cpp
Revision: 1.10
Committed: 2009-07-31T20:43:28Z (14 years, 9 months ago) by asvitkine
Branch: MAIN
Changes since 1.9: +3 -16 lines
Log Message:
correct implementation of PrimeTime(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 timer_mac2host_time(delay, ReadMacInt16(tm + tmCount));
432
433 // Yes, calculate wakeup time relative to last scheduled time
434 tm_time_t wakeup;
435 timer_add_time(wakeup, desc[i].wakeup, delay);
436 desc[i].wakeup = wakeup;
437
438 } else {
439
440 // No, calculate wakeup time relative to current time
441 tm_time_t now;
442 timer_current_time(now);
443 timer_add_time(desc[i].wakeup, now, delay);
444 }
445
446 // Set tmWakeUp to indicate that task was scheduled
447 WriteMacInt32(tm + tmWakeUp, 0x12345678);
448
449 } else {
450
451 // Not extended task, calculate wakeup time relative to current time
452 tm_time_t now;
453 timer_current_time(now);
454 timer_add_time(desc[i].wakeup, now, delay);
455 }
456
457 // Make task active and enqueue it in the Time Manager queue
458 #if PRECISE_TIMING_BEOS
459 while (acquire_sem(wakeup_time_sem) == B_INTERRUPTED) ;
460 suspend_thread(timer_thread);
461 #endif
462 #if PRECISE_TIMING_POSIX
463 timer_thread_suspend();
464 pthread_mutex_lock(&wakeup_time_lock);
465 #endif
466 WriteMacInt16(tm + qType, ReadMacInt16(tm + qType) | 0x8000);
467 enqueue_tm(tm);
468 #if PRECISE_TIMING
469 // Look for next task to be called and set wakeup_time
470 wakeup_time = wakeup_time_max;
471 for (int j=0; j<NUM_DESCS; j++) {
472 if (desc[j].in_use && (ReadMacInt16(desc[j].task + qType) & 0x8000))
473 if (timer_cmp_time(desc[j].wakeup, wakeup_time) < 0)
474 wakeup_time = desc[j].wakeup;
475 }
476 #ifdef PRECISE_TIMING_BEOS
477 release_sem(wakeup_time_sem);
478 thread_info info;
479 do {
480 resume_thread(timer_thread); // This will unblock the thread
481 get_thread_info(timer_thread, &info);
482 } while (info.state == B_THREAD_SUSPENDED); // Sometimes, resume_thread() doesn't work (BeOS bug?)
483 #endif
484 #ifdef PRECISE_TIMING_POSIX
485 pthread_mutex_unlock(&wakeup_time_lock);
486 timer_thread_resume();
487 assert(suspend_count == 0);
488 #endif
489 #endif
490 return 0;
491 }
492
493
494 /*
495 * Time Manager thread
496 */
497
498 #ifdef PRECISE_TIMING_BEOS
499 static int32 timer_func(void *arg)
500 {
501 while (thread_active) {
502
503 // Wait until time specified by wakeup_time
504 snooze_until(wakeup_time, B_SYSTEM_TIMEBASE);
505
506 while (acquire_sem(wakeup_time_sem) == B_INTERRUPTED) ;
507 if (wakeup_time < system_time()) {
508
509 // Timer expired, trigger interrupt
510 wakeup_time = 0x7fffffffffffffff;
511 SetInterruptFlag(INTFLAG_TIMER);
512 TriggerInterrupt();
513 }
514 release_sem(wakeup_time_sem);
515 }
516 return 0;
517 }
518 #endif
519
520 #ifdef PRECISE_TIMING_POSIX
521 static void *timer_func(void *arg)
522 {
523 while (!timer_thread_cancel) {
524
525 // Wait until time specified by wakeup_time
526 clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &wakeup_time, NULL);
527
528 tm_time_t system_time;
529 timer_current_time(system_time);
530 if (timer_cmp_time(wakeup_time, system_time) < 0) {
531
532 // Timer expired, trigger interrupt
533 pthread_mutex_lock(&wakeup_time_lock);
534 wakeup_time = wakeup_time_max;
535 pthread_mutex_unlock(&wakeup_time_lock);
536 SetInterruptFlag(INTFLAG_TIMER);
537 TriggerInterrupt();
538 }
539 }
540 return NULL;
541 }
542 #endif
543
544
545 /*
546 * Timer interrupt function (executed as part of 60Hz interrupt)
547 */
548
549 void TimerInterrupt(void)
550 {
551 // D(bug("TimerIRQ\n"));
552
553 // Look for active TMTasks that have expired
554 tm_time_t now;
555 timer_current_time(now);
556 for (int i=0; i<NUM_DESCS; i++)
557 if (desc[i].in_use) {
558 uint32 tm = desc[i].task;
559 if ((ReadMacInt16(tm + qType) & 0x8000) && timer_cmp_time(desc[i].wakeup, now) <= 0) {
560
561 // Found one, mark as inactive and remove it from the Time Manager queue
562 WriteMacInt16(tm + qType, ReadMacInt16(tm + qType) & 0x7fff);
563 dequeue_tm(tm);
564
565 // Call timer function
566 uint32 addr = ReadMacInt32(tm + tmAddr);
567 if (addr) {
568 D(bug("Calling TimeTask %08lx, addr %08lx\n", tm, addr));
569 M68kRegisters r;
570 r.a[0] = addr;
571 r.a[1] = tm;
572 Execute68k(r.a[0], &r);
573 D(bug(" returned from TimeTask\n"));
574 }
575 }
576 }
577
578 #if PRECISE_TIMING
579 // Look for next task to be called and set wakeup_time
580 #if PRECISE_TIMING_BEOS
581 while (acquire_sem(wakeup_time_sem) == B_INTERRUPTED) ;
582 suspend_thread(timer_thread);
583 #endif
584 #if PRECISE_TIMING_POSIX
585 timer_thread_suspend();
586 pthread_mutex_lock(&wakeup_time_lock);
587 #endif
588 wakeup_time = wakeup_time_max;
589 for (int j=0; j<NUM_DESCS; j++) {
590 if (desc[j].in_use && (ReadMacInt16(desc[j].task + qType) & 0x8000))
591 if (timer_cmp_time(desc[j].wakeup, wakeup_time) < 0)
592 wakeup_time = desc[j].wakeup;
593 }
594 #if PRECISE_TIMING_BEOS
595 release_sem(wakeup_time_sem);
596 thread_info info;
597 do {
598 resume_thread(timer_thread); // This will unblock the thread
599 get_thread_info(timer_thread, &info);
600 } while (info.state == B_THREAD_SUSPENDED); // Sometimes, resume_thread() doesn't work (BeOS bug?)
601 #endif
602 #if PRECISE_TIMING_POSIX
603 pthread_mutex_unlock(&wakeup_time_lock);
604 timer_thread_resume();
605 assert(suspend_count == 0);
606 #endif
607 #endif
608 }