ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/cebix/SheepShaver/src/timer.cpp
Revision: 1.6
Committed: 2005-03-16T00:35:51Z (19 years, 8 months ago) by gbeauche
Branch: MAIN
Changes since 1.5: +12 -13 lines
Log Message:
Use a mutex to protect wakeup_time instead of a semaphore, do we want some
sort of barrier synchronisation? Anyhow, the current implementation looks
reasonable enough now when using old LinuxThreads without TLS.

XXX: in the past, the sem_post in sigsuspend_handler would not release the
calling thread thus entering into a mutual waiting condition.

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 gbeauche 1.5 static bool timer_thread_active = false;
74     static volatile bool timer_thread_cancel = false;
75 gbeauche 1.4 static tm_time_t wakeup_time_max = { 0x7fffffff, 999999999 };
76     static tm_time_t wakeup_time = wakeup_time_max;
77 gbeauche 1.6 static pthread_mutex_t wakeup_time_lock = PTHREAD_MUTEX_INITIALIZER;
78 gbeauche 1.4 static void *timer_func(void *arg);
79     #endif
80     #endif
81 cebix 1.1
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 gbeauche 1.4 * 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 gbeauche 1.5 static sigset_t suspend_handler_mask;
170 gbeauche 1.4
171     // Signal handler for suspended thread
172     static void sigsuspend_handler(int sig)
173     {
174     sem_post(&suspend_ack_sem);
175 gbeauche 1.5 sigsuspend(&suspend_handler_mask);
176 gbeauche 1.4 }
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 gbeauche 1.6 sigemptyset(&sigsuspend_action.sa_mask);
189     sigaddset(&sigsuspend_action.sa_mask, SIGRESUME);
190 gbeauche 1.4 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 gbeauche 1.6 sigemptyset(&sigresume_action.sa_mask);
200 gbeauche 1.4 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 gbeauche 1.5 // 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 gbeauche 1.4 // 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 gbeauche 1.5 timer_thread_cancel = true;
227 gbeauche 1.4 #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 cebix 1.1 * 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 gbeauche 1.4 #ifdef PRECISE_TIMING_BEOS
272 cebix 1.1 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 gbeauche 1.4 #ifdef PRECISE_TIMING_POSIX
277 gbeauche 1.5 timer_thread_active = timer_thread_init();
278 gbeauche 1.4 #endif
279     #endif
280 cebix 1.1 }
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 gbeauche 1.4 #ifdef PRECISE_TIMING_BEOS
293 cebix 1.1 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 gbeauche 1.4 #endif
300     #ifdef PRECISE_TIMING_POSIX
301     timer_thread_kill();
302     #endif
303 cebix 1.1 }
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 gbeauche 1.4 #if PRECISE_TIMING_BEOS
356 cebix 1.1 while (acquire_sem(wakeup_time_sem) == B_INTERRUPTED) ;
357     suspend_thread(timer_thread);
358     #endif
359 gbeauche 1.4 #if PRECISE_TIMING_POSIX
360     timer_thread_suspend();
361 gbeauche 1.6 pthread_mutex_lock(&wakeup_time_lock);
362 gbeauche 1.4 #endif
363 cebix 1.1 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 gbeauche 1.4 wakeup_time = wakeup_time_max;
371 cebix 1.1 for (int j=0; j<NUM_DESCS; j++) {
372     if (desc[j].in_use && (ReadMacInt16(desc[j].task + qType) & 0x8000))
373 gbeauche 1.4 if (timer_cmp_time(desc[j].wakeup, wakeup_time) < 0)
374 cebix 1.1 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 gbeauche 1.4 #if PRECISE_TIMING_BEOS
387 cebix 1.1 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 gbeauche 1.4 #if PRECISE_TIMING_POSIX
395 gbeauche 1.6 pthread_mutex_unlock(&wakeup_time_lock);
396 gbeauche 1.4 timer_thread_resume();
397     assert(suspend_count == 0);
398     #endif
399 cebix 1.1
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 gbeauche 1.4 #if PRECISE_TIMING_BEOS
464 cebix 1.1 while (acquire_sem(wakeup_time_sem) == B_INTERRUPTED) ;
465     suspend_thread(timer_thread);
466     #endif
467 gbeauche 1.4 #if PRECISE_TIMING_POSIX
468     timer_thread_suspend();
469 gbeauche 1.6 pthread_mutex_lock(&wakeup_time_lock);
470 gbeauche 1.4 #endif
471 cebix 1.1 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 gbeauche 1.4 wakeup_time = wakeup_time_max;
476 cebix 1.1 for (int j=0; j<NUM_DESCS; j++) {
477     if (desc[j].in_use && (ReadMacInt16(desc[j].task + qType) & 0x8000))
478 gbeauche 1.4 if (timer_cmp_time(desc[j].wakeup, wakeup_time) < 0)
479 cebix 1.1 wakeup_time = desc[j].wakeup;
480     }
481 gbeauche 1.4 #ifdef PRECISE_TIMING_BEOS
482 cebix 1.1 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 gbeauche 1.4 #ifdef PRECISE_TIMING_POSIX
490 gbeauche 1.6 pthread_mutex_unlock(&wakeup_time_lock);
491 gbeauche 1.4 timer_thread_resume();
492     assert(suspend_count == 0);
493     #endif
494     #endif
495 cebix 1.1 return 0;
496     }
497    
498    
499     /*
500     * Time Manager thread
501     */
502    
503 gbeauche 1.4 #ifdef PRECISE_TIMING_BEOS
504 cebix 1.1 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 gbeauche 1.4 #ifdef PRECISE_TIMING_POSIX
526     static void *timer_func(void *arg)
527     {
528 gbeauche 1.5 while (!timer_thread_cancel) {
529 gbeauche 1.4
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 gbeauche 1.6 pthread_mutex_lock(&wakeup_time_lock);
536 gbeauche 1.4 if (timer_cmp_time(wakeup_time, system_time) < 0) {
537    
538     // Timer expired, trigger interrupt
539     wakeup_time = wakeup_time_max;
540     SetInterruptFlag(INTFLAG_TIMER);
541     TriggerInterrupt();
542     }
543 gbeauche 1.6 pthread_mutex_unlock(&wakeup_time_lock);
544 gbeauche 1.4 }
545     return NULL;
546     }
547     #endif
548    
549 cebix 1.1
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 gbeauche 1.4 #if PRECISE_TIMING_BEOS
586 cebix 1.1 while (acquire_sem(wakeup_time_sem) == B_INTERRUPTED) ;
587     suspend_thread(timer_thread);
588 gbeauche 1.4 #endif
589     #if PRECISE_TIMING_POSIX
590     timer_thread_suspend();
591 gbeauche 1.6 pthread_mutex_lock(&wakeup_time_lock);
592 gbeauche 1.4 #endif
593     wakeup_time = wakeup_time_max;
594 cebix 1.1 for (int j=0; j<NUM_DESCS; j++) {
595     if (desc[j].in_use && (ReadMacInt16(desc[j].task + qType) & 0x8000))
596 gbeauche 1.4 if (timer_cmp_time(desc[j].wakeup, wakeup_time) < 0)
597 cebix 1.1 wakeup_time = desc[j].wakeup;
598     }
599 gbeauche 1.4 #if PRECISE_TIMING_BEOS
600 cebix 1.1 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 gbeauche 1.4 #if PRECISE_TIMING_POSIX
608 gbeauche 1.6 pthread_mutex_unlock(&wakeup_time_lock);
609 gbeauche 1.4 timer_thread_resume();
610     assert(suspend_count == 0);
611     #endif
612     #endif
613 cebix 1.1 }