ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/cebix/SheepShaver/src/timer.cpp
Revision: 1.5
Committed: 2005-03-15T20:46:50Z (19 years, 8 months ago) by gbeauche
Branch: MAIN
Changes since 1.4: +13 -9 lines
Log Message:
fix initialization of timer thread that could be put in indefinite suspended
state from the start.

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     static sem_t wakeup_time_sem;
78     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     sigfillset(&sigsuspend_action.sa_mask);
189     sigsuspend_action.sa_handler = sigsuspend_handler;
190     sigsuspend_action.sa_flags = SA_RESTART;
191     #ifdef HAVE_SIGNAL_SA_RESTORER
192     sigsuspend_action.sa_restorer = NULL;
193     #endif
194     if (sigaction(SIGSUSPEND, &sigsuspend_action, NULL) < 0)
195     return false;
196    
197     // Install resume signal handler
198     sigfillset(&sigresume_action.sa_mask);
199     sigresume_action.sa_handler = sigresume_handler;
200     sigresume_action.sa_flags = SA_RESTART;
201     #ifdef HAVE_SIGNAL_SA_RESTORER
202     sigresume_action.sa_restorer = NULL;
203     #endif
204     if (sigaction(SIGRESUME, &sigresume_action, NULL) < 0)
205     return false;
206    
207     // Initialize semaphore
208     if (sem_init(&suspend_ack_sem, 0, 0) < 0)
209     return false;
210    
211 gbeauche 1.5 // Initialize suspend_handler_mask, it excludes SIGRESUME
212     if (sigfillset(&suspend_handler_mask) != 0)
213     return false;
214     if (sigdelset(&suspend_handler_mask, SIGRESUME) != 0)
215     return false;
216    
217 gbeauche 1.4 // Create thread in running state
218     suspend_count = 0;
219     return (pthread_create(&timer_thread, NULL, timer_func, NULL) == 0);
220     }
221    
222     // Kill timer thread
223     static void timer_thread_kill(void)
224     {
225 gbeauche 1.5 timer_thread_cancel = true;
226 gbeauche 1.4 #ifdef HAVE_PTHREAD_CANCEL
227     pthread_cancel(timer_thread);
228     #endif
229     pthread_join(timer_thread, NULL);
230     }
231    
232     // Suspend timer thread
233     static void timer_thread_suspend(void)
234     {
235     pthread_mutex_lock(&suspend_count_lock);
236     if (suspend_count == 0) {
237     suspend_count ++;
238     if (pthread_kill(timer_thread, SIGSUSPEND) == 0)
239     sem_wait(&suspend_ack_sem);
240     }
241     pthread_mutex_unlock(&suspend_count_lock);
242     }
243    
244     // Resume timer thread
245     static void timer_thread_resume(void)
246     {
247     pthread_mutex_lock(&suspend_count_lock);
248     assert(suspend_count > 0);
249     if (suspend_count == 1) {
250     suspend_count = 0;
251     pthread_kill(timer_thread, SIGRESUME);
252     }
253     pthread_mutex_unlock(&suspend_count_lock);
254     }
255     #endif
256    
257    
258     /*
259 cebix 1.1 * Initialize Time Manager
260     */
261    
262     void TimerInit(void)
263     {
264     // Mark all descriptors as inactive
265     for (int i=0; i<NUM_DESCS; i++)
266     free_desc(i);
267    
268     #if PRECISE_TIMING
269     // Start timer thread
270 gbeauche 1.4 #ifdef PRECISE_TIMING_BEOS
271 cebix 1.1 wakeup_time_sem = create_sem(1, "Wakeup Time");
272     timer_thread = spawn_thread(timer_func, "Time Manager", B_REAL_TIME_PRIORITY, NULL);
273     resume_thread(timer_thread);
274     #endif
275 gbeauche 1.4 #ifdef PRECISE_TIMING_POSIX
276     sem_init(&wakeup_time_sem, 0, 1);
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     sem_destroy(&wakeup_time_sem);
303     #endif
304 cebix 1.1 }
305     #endif
306     }
307    
308    
309     /*
310     * Emulator reset, remove all timer tasks
311     */
312    
313     void TimerReset(void)
314     {
315     // Mark all descriptors as inactive
316     for (int i=0; i<NUM_DESCS; i++)
317     free_desc(i);
318     }
319    
320    
321     /*
322     * Insert timer task
323     */
324    
325     int16 InsTime(uint32 tm, uint16 trap)
326     {
327     D(bug("InsTime %08lx, trap %04x\n", tm, trap));
328     WriteMacInt16((uint32)tm + qType, ReadMacInt16((uint32)tm + qType) & 0x1fff | (trap << 4) & 0x6000);
329     if (find_desc(tm) >= 0)
330     printf("WARNING: InsTime(): Task re-inserted\n");
331     else {
332     int i = alloc_desc(tm);
333     if (i < 0)
334     printf("FATAL: InsTime(): No free Time Manager descriptor\n");
335     }
336     return 0;
337     }
338    
339    
340     /*
341     * Remove timer task
342     */
343    
344     int16 RmvTime(uint32 tm)
345     {
346     D(bug("RmvTime %08lx\n", tm));
347    
348     // Find descriptor
349     int i = find_desc(tm);
350     if (i < 0) {
351     printf("WARNING: RmvTime(%08lx): Descriptor not found\n", tm);
352     return 0;
353     }
354    
355     // Task active?
356 gbeauche 1.4 #if PRECISE_TIMING_BEOS
357 cebix 1.1 while (acquire_sem(wakeup_time_sem) == B_INTERRUPTED) ;
358     suspend_thread(timer_thread);
359     #endif
360 gbeauche 1.4 #if PRECISE_TIMING_POSIX
361     sem_wait(&wakeup_time_sem);
362     timer_thread_suspend();
363     #endif
364 cebix 1.1 if (ReadMacInt16(tm + qType) & 0x8000) {
365    
366     // Yes, make task inactive and remove it from the Time Manager queue
367     WriteMacInt16(tm + qType, ReadMacInt16(tm + qType) & 0x7fff);
368     dequeue_tm(tm);
369     #if PRECISE_TIMING
370     // Look for next task to be called and set wakeup_time
371 gbeauche 1.4 wakeup_time = wakeup_time_max;
372 cebix 1.1 for (int j=0; j<NUM_DESCS; j++) {
373     if (desc[j].in_use && (ReadMacInt16(desc[j].task + qType) & 0x8000))
374 gbeauche 1.4 if (timer_cmp_time(desc[j].wakeup, wakeup_time) < 0)
375 cebix 1.1 wakeup_time = desc[j].wakeup;
376     }
377     #endif
378    
379     // Compute remaining time
380     tm_time_t remaining, current;
381     timer_current_time(current);
382     timer_sub_time(remaining, desc[i].wakeup, current);
383     WriteMacInt32(tm + tmCount, timer_host2mac_time(remaining));
384     } else
385     WriteMacInt32(tm + tmCount, 0);
386     D(bug(" tmCount %ld\n", ReadMacInt32(tm + tmCount)));
387 gbeauche 1.4 #if PRECISE_TIMING_BEOS
388 cebix 1.1 release_sem(wakeup_time_sem);
389     thread_info info;
390     do {
391     resume_thread(timer_thread); // This will unblock the thread
392     get_thread_info(timer_thread, &info);
393     } while (info.state == B_THREAD_SUSPENDED); // Sometimes, resume_thread() doesn't work (BeOS bug?)
394     #endif
395 gbeauche 1.4 #if PRECISE_TIMING_POSIX
396     sem_post(&wakeup_time_sem);
397     timer_thread_resume();
398     assert(suspend_count == 0);
399     #endif
400 cebix 1.1
401     // Free descriptor
402     free_desc(i);
403     return 0;
404     }
405    
406    
407     /*
408     * Start timer task
409     */
410    
411     int16 PrimeTime(uint32 tm, int32 time)
412     {
413     D(bug("PrimeTime %08lx, time %ld\n", tm, time));
414    
415     // Find descriptor
416     int i = find_desc(tm);
417     if (i < 0) {
418     printf("FATAL: PrimeTime(): Descriptor not found\n");
419     return 0;
420     }
421    
422     // Convert delay time
423     tm_time_t delay;
424     timer_mac2host_time(delay, time);
425    
426     // Extended task?
427     if (ReadMacInt16(tm + qType) & 0x4000) {
428    
429     // Yes, tmWakeUp set?
430     if (ReadMacInt32(tm + tmWakeUp)) {
431    
432     //!! PrimeTime(0) means continue previous delay
433     // (save wakeup time in RmvTime?)
434     if (time == 0) {
435     printf("FATAL: Unsupported PrimeTime(0)\n");
436     return 0;
437     }
438    
439     // Yes, calculate wakeup time relative to last scheduled time
440     tm_time_t wakeup;
441     timer_add_time(wakeup, desc[i].wakeup, delay);
442     desc[i].wakeup = wakeup;
443    
444     } else {
445    
446     // No, calculate wakeup time relative to current time
447     tm_time_t now;
448     timer_current_time(now);
449     timer_add_time(desc[i].wakeup, now, delay);
450     }
451    
452     // Set tmWakeUp to indicate that task was scheduled
453     WriteMacInt32(tm + tmWakeUp, 0x12345678);
454    
455     } else {
456    
457     // Not extended task, calculate wakeup time relative to current time
458     tm_time_t now;
459     timer_current_time(now);
460     timer_add_time(desc[i].wakeup, now, delay);
461     }
462    
463     // Make task active and enqueue it in the Time Manager queue
464 gbeauche 1.4 #if PRECISE_TIMING_BEOS
465 cebix 1.1 while (acquire_sem(wakeup_time_sem) == B_INTERRUPTED) ;
466     suspend_thread(timer_thread);
467     #endif
468 gbeauche 1.4 #if PRECISE_TIMING_POSIX
469     sem_wait(&wakeup_time_sem);
470     timer_thread_suspend();
471     #endif
472 cebix 1.1 WriteMacInt16(tm + qType, ReadMacInt16(tm + qType) | 0x8000);
473     enqueue_tm(tm);
474     #if PRECISE_TIMING
475     // Look for next task to be called and set wakeup_time
476 gbeauche 1.4 wakeup_time = wakeup_time_max;
477 cebix 1.1 for (int j=0; j<NUM_DESCS; j++) {
478     if (desc[j].in_use && (ReadMacInt16(desc[j].task + qType) & 0x8000))
479 gbeauche 1.4 if (timer_cmp_time(desc[j].wakeup, wakeup_time) < 0)
480 cebix 1.1 wakeup_time = desc[j].wakeup;
481     }
482 gbeauche 1.4 #ifdef PRECISE_TIMING_BEOS
483 cebix 1.1 release_sem(wakeup_time_sem);
484     thread_info info;
485     do {
486     resume_thread(timer_thread); // This will unblock the thread
487     get_thread_info(timer_thread, &info);
488     } while (info.state == B_THREAD_SUSPENDED); // Sometimes, resume_thread() doesn't work (BeOS bug?)
489     #endif
490 gbeauche 1.4 #ifdef PRECISE_TIMING_POSIX
491     sem_post(&wakeup_time_sem);
492     timer_thread_resume();
493     assert(suspend_count == 0);
494     #endif
495     #endif
496 cebix 1.1 return 0;
497     }
498    
499    
500     /*
501     * Time Manager thread
502     */
503    
504 gbeauche 1.4 #ifdef PRECISE_TIMING_BEOS
505 cebix 1.1 static int32 timer_func(void *arg)
506     {
507     while (thread_active) {
508    
509     // Wait until time specified by wakeup_time
510     snooze_until(wakeup_time, B_SYSTEM_TIMEBASE);
511    
512     while (acquire_sem(wakeup_time_sem) == B_INTERRUPTED) ;
513     if (wakeup_time < system_time()) {
514    
515     // Timer expired, trigger interrupt
516     wakeup_time = 0x7fffffffffffffff;
517     SetInterruptFlag(INTFLAG_TIMER);
518     TriggerInterrupt();
519     }
520     release_sem(wakeup_time_sem);
521     }
522     return 0;
523     }
524     #endif
525    
526 gbeauche 1.4 #ifdef PRECISE_TIMING_POSIX
527     static void *timer_func(void *arg)
528     {
529 gbeauche 1.5 while (!timer_thread_cancel) {
530 gbeauche 1.4
531     // Wait until time specified by wakeup_time
532     clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &wakeup_time, NULL);
533    
534     sem_wait(&wakeup_time_sem);
535     tm_time_t system_time;
536     timer_current_time(system_time);
537     if (timer_cmp_time(wakeup_time, system_time) < 0) {
538    
539     // Timer expired, trigger interrupt
540     wakeup_time = wakeup_time_max;
541     SetInterruptFlag(INTFLAG_TIMER);
542     TriggerInterrupt();
543     }
544     sem_post(&wakeup_time_sem);
545     }
546     return NULL;
547     }
548     #endif
549    
550 cebix 1.1
551     /*
552     * Timer interrupt function (executed as part of 60Hz interrupt)
553     */
554    
555     void TimerInterrupt(void)
556     {
557     // D(bug("TimerIRQ\n"));
558    
559     // Look for active TMTasks that have expired
560     tm_time_t now;
561     timer_current_time(now);
562     for (int i=0; i<NUM_DESCS; i++)
563     if (desc[i].in_use) {
564     uint32 tm = desc[i].task;
565     if ((ReadMacInt16(tm + qType) & 0x8000) && timer_cmp_time(desc[i].wakeup, now) <= 0) {
566    
567     // Found one, mark as inactive and remove it from the Time Manager queue
568     WriteMacInt16(tm + qType, ReadMacInt16(tm + qType) & 0x7fff);
569     dequeue_tm(tm);
570    
571     // Call timer function
572     uint32 addr = ReadMacInt32(tm + tmAddr);
573     if (addr) {
574     D(bug("Calling TimeTask %08lx, addr %08lx\n", tm, addr));
575     M68kRegisters r;
576     r.a[0] = addr;
577     r.a[1] = tm;
578     Execute68k(r.a[0], &r);
579     D(bug(" returned from TimeTask\n"));
580     }
581     }
582     }
583    
584     #if PRECISE_TIMING
585     // Look for next task to be called and set wakeup_time
586 gbeauche 1.4 #if PRECISE_TIMING_BEOS
587 cebix 1.1 while (acquire_sem(wakeup_time_sem) == B_INTERRUPTED) ;
588     suspend_thread(timer_thread);
589 gbeauche 1.4 #endif
590     #if PRECISE_TIMING_POSIX
591     sem_wait(&wakeup_time_sem);
592     timer_thread_suspend();
593     #endif
594     wakeup_time = wakeup_time_max;
595 cebix 1.1 for (int j=0; j<NUM_DESCS; j++) {
596     if (desc[j].in_use && (ReadMacInt16(desc[j].task + qType) & 0x8000))
597 gbeauche 1.4 if (timer_cmp_time(desc[j].wakeup, wakeup_time) < 0)
598 cebix 1.1 wakeup_time = desc[j].wakeup;
599     }
600 gbeauche 1.4 #if PRECISE_TIMING_BEOS
601 cebix 1.1 release_sem(wakeup_time_sem);
602     thread_info info;
603     do {
604     resume_thread(timer_thread); // This will unblock the thread
605     get_thread_info(timer_thread, &info);
606     } while (info.state == B_THREAD_SUSPENDED); // Sometimes, resume_thread() doesn't work (BeOS bug?)
607     #endif
608 gbeauche 1.4 #if PRECISE_TIMING_POSIX
609     sem_post(&wakeup_time_sem);
610     timer_thread_resume();
611     assert(suspend_count == 0);
612     #endif
613     #endif
614 cebix 1.1 }