ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/cebix/SheepShaver/src/timer.cpp
Revision: 1.7
Committed: 2005-07-01T23:15:11Z (18 years, 10 months ago) by gbeauche
Branch: MAIN
Changes since 1.6: +2 -2 lines
Log Message:
Hopefully fix the remaining issue in the High Resolution Timing support
code and re-enable it on Linux platforms (they have clock_nanosleep). Why
did I trigger an interrupt inside a held lock? Hmmm, we should probably
add an _ack semaphore like we do e.g. for ethernet.

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