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

# User Rev Content
1 cebix 1.1 /*
2     * timer.cpp - Time Manager emulation
3     *
4 gbeauche 1.3 * SheepShaver (C) 1997-2005 Christian Bauer and Marc Hellwig
5 cebix 1.1 *
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 gbeauche 1.4 #ifdef PRECISE_TIMING_POSIX
32     #include <pthread.h>
33     #include <semaphore.h>
34     #endif
35    
36 cebix 1.1 #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 gbeauche 1.4 #ifdef PRECISE_TIMING_BEOS
64 cebix 1.1 static thread_id timer_thread = -1;
65     static bool thread_active = true;
66 gbeauche 1.4 static const tm_time_t wakeup_time_max = 0x7fffffffffffffff;
67     static volatile tm_time_t wakeup_time = wakeup_time_max;
68 cebix 1.1 static sem_id wakeup_time_sem = -1;
69     static int32 timer_func(void *arg);
70     #endif
71 gbeauche 1.4 #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 cebix 1.1
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 gbeauche 1.4 * 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 cebix 1.1 * 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 gbeauche 1.4 #ifdef PRECISE_TIMING_BEOS
266 cebix 1.1 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 gbeauche 1.4 #ifdef PRECISE_TIMING_POSIX
271     sem_init(&wakeup_time_sem, 0, 1);
272     thread_active = timer_thread_init();
273     #endif
274     #endif
275 cebix 1.1 }
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 gbeauche 1.4 #ifdef PRECISE_TIMING_BEOS
288 cebix 1.1 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 gbeauche 1.4 #endif
295     #ifdef PRECISE_TIMING_POSIX
296     thread_active = false;
297     timer_thread_kill();
298     sem_destroy(&wakeup_time_sem);
299     #endif
300 cebix 1.1 }
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 gbeauche 1.4 #if PRECISE_TIMING_BEOS
353 cebix 1.1 while (acquire_sem(wakeup_time_sem) == B_INTERRUPTED) ;
354     suspend_thread(timer_thread);
355     #endif
356 gbeauche 1.4 #if PRECISE_TIMING_POSIX
357     sem_wait(&wakeup_time_sem);
358     timer_thread_suspend();
359     #endif
360 cebix 1.1 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 gbeauche 1.4 wakeup_time = wakeup_time_max;
368 cebix 1.1 for (int j=0; j<NUM_DESCS; j++) {
369     if (desc[j].in_use && (ReadMacInt16(desc[j].task + qType) & 0x8000))
370 gbeauche 1.4 if (timer_cmp_time(desc[j].wakeup, wakeup_time) < 0)
371 cebix 1.1 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 gbeauche 1.4 #if PRECISE_TIMING_BEOS
384 cebix 1.1 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 gbeauche 1.4 #if PRECISE_TIMING_POSIX
392     sem_post(&wakeup_time_sem);
393     timer_thread_resume();
394     assert(suspend_count == 0);
395     #endif
396 cebix 1.1
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 gbeauche 1.4 #if PRECISE_TIMING_BEOS
461 cebix 1.1 while (acquire_sem(wakeup_time_sem) == B_INTERRUPTED) ;
462     suspend_thread(timer_thread);
463     #endif
464 gbeauche 1.4 #if PRECISE_TIMING_POSIX
465     sem_wait(&wakeup_time_sem);
466     timer_thread_suspend();
467     #endif
468 cebix 1.1 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 gbeauche 1.4 wakeup_time = wakeup_time_max;
473 cebix 1.1 for (int j=0; j<NUM_DESCS; j++) {
474     if (desc[j].in_use && (ReadMacInt16(desc[j].task + qType) & 0x8000))
475 gbeauche 1.4 if (timer_cmp_time(desc[j].wakeup, wakeup_time) < 0)
476 cebix 1.1 wakeup_time = desc[j].wakeup;
477     }
478 gbeauche 1.4 #ifdef PRECISE_TIMING_BEOS
479 cebix 1.1 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 gbeauche 1.4 #ifdef PRECISE_TIMING_POSIX
487     sem_post(&wakeup_time_sem);
488     timer_thread_resume();
489     assert(suspend_count == 0);
490     #endif
491     #endif
492 cebix 1.1 return 0;
493     }
494    
495    
496     /*
497     * Time Manager thread
498     */
499    
500 gbeauche 1.4 #ifdef PRECISE_TIMING_BEOS
501 cebix 1.1 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 gbeauche 1.4 #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 cebix 1.1
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 gbeauche 1.4 #if PRECISE_TIMING_BEOS
583 cebix 1.1 while (acquire_sem(wakeup_time_sem) == B_INTERRUPTED) ;
584     suspend_thread(timer_thread);
585 gbeauche 1.4 #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 cebix 1.1 for (int j=0; j<NUM_DESCS; j++) {
592     if (desc[j].in_use && (ReadMacInt16(desc[j].task + qType) & 0x8000))
593 gbeauche 1.4 if (timer_cmp_time(desc[j].wakeup, wakeup_time) < 0)
594 cebix 1.1 wakeup_time = desc[j].wakeup;
595     }
596 gbeauche 1.4 #if PRECISE_TIMING_BEOS
597 cebix 1.1 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 gbeauche 1.4 #if PRECISE_TIMING_POSIX
605     sem_post(&wakeup_time_sem);
606     timer_thread_resume();
607     assert(suspend_count == 0);
608     #endif
609     #endif
610 cebix 1.1 }