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 (15 years, 3 months ago) by asvitkine
Branch: MAIN
Changes since 1.9: +3 -16 lines
Log Message:
correct implementation of PrimeTime(0)

File Contents

# User Rev Content
1 cebix 1.1 /*
2     * timer.cpp - Time Manager emulation
3     *
4 gbeauche 1.8 * SheepShaver (C) 1997-2008 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     #include "sysdeps.h"
22     #include "timer.h"
23     #include "macos_util.h"
24     #include "main.h"
25     #include "cpu_emulation.h"
26    
27 gbeauche 1.4 #ifdef PRECISE_TIMING_POSIX
28     #include <pthread.h>
29     #include <semaphore.h>
30     #endif
31    
32 cebix 1.1 #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 gbeauche 1.4 #ifdef PRECISE_TIMING_BEOS
60 cebix 1.1 static thread_id timer_thread = -1;
61     static bool thread_active = true;
62 gbeauche 1.4 static const tm_time_t wakeup_time_max = 0x7fffffffffffffff;
63     static volatile tm_time_t wakeup_time = wakeup_time_max;
64 cebix 1.1 static sem_id wakeup_time_sem = -1;
65     static int32 timer_func(void *arg);
66     #endif
67 gbeauche 1.4 #ifdef PRECISE_TIMING_POSIX
68     static pthread_t timer_thread;
69 gbeauche 1.5 static bool timer_thread_active = false;
70     static volatile bool timer_thread_cancel = false;
71 gbeauche 1.4 static tm_time_t wakeup_time_max = { 0x7fffffff, 999999999 };
72     static tm_time_t wakeup_time = wakeup_time_max;
73 gbeauche 1.6 static pthread_mutex_t wakeup_time_lock = PTHREAD_MUTEX_INITIALIZER;
74 gbeauche 1.4 static void *timer_func(void *arg);
75     #endif
76     #endif
77 cebix 1.1
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 gbeauche 1.4 * 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 gbeauche 1.5 static sigset_t suspend_handler_mask;
166 gbeauche 1.4
167     // Signal handler for suspended thread
168     static void sigsuspend_handler(int sig)
169     {
170     sem_post(&suspend_ack_sem);
171 gbeauche 1.5 sigsuspend(&suspend_handler_mask);
172 gbeauche 1.4 }
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 gbeauche 1.6 sigemptyset(&sigsuspend_action.sa_mask);
185     sigaddset(&sigsuspend_action.sa_mask, SIGRESUME);
186 gbeauche 1.4 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 gbeauche 1.6 sigemptyset(&sigresume_action.sa_mask);
196 gbeauche 1.4 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 gbeauche 1.5 // 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 gbeauche 1.4 // 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 gbeauche 1.5 timer_thread_cancel = true;
223 gbeauche 1.4 #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 cebix 1.1 * 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 gbeauche 1.4 #ifdef PRECISE_TIMING_BEOS
268 cebix 1.1 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 gbeauche 1.4 #ifdef PRECISE_TIMING_POSIX
273 gbeauche 1.5 timer_thread_active = timer_thread_init();
274 gbeauche 1.4 #endif
275     #endif
276 cebix 1.1 }
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 gbeauche 1.4 #ifdef PRECISE_TIMING_BEOS
289 cebix 1.1 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 gbeauche 1.4 #endif
296     #ifdef PRECISE_TIMING_POSIX
297     timer_thread_kill();
298     #endif
299 cebix 1.1 }
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 gbeauche 1.4 #if PRECISE_TIMING_BEOS
352 cebix 1.1 while (acquire_sem(wakeup_time_sem) == B_INTERRUPTED) ;
353     suspend_thread(timer_thread);
354     #endif
355 gbeauche 1.4 #if PRECISE_TIMING_POSIX
356     timer_thread_suspend();
357 gbeauche 1.6 pthread_mutex_lock(&wakeup_time_lock);
358 gbeauche 1.4 #endif
359 cebix 1.1 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 gbeauche 1.4 wakeup_time = wakeup_time_max;
367 cebix 1.1 for (int j=0; j<NUM_DESCS; j++) {
368     if (desc[j].in_use && (ReadMacInt16(desc[j].task + qType) & 0x8000))
369 gbeauche 1.4 if (timer_cmp_time(desc[j].wakeup, wakeup_time) < 0)
370 cebix 1.1 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 gbeauche 1.4 #if PRECISE_TIMING_BEOS
383 cebix 1.1 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 gbeauche 1.4 #if PRECISE_TIMING_POSIX
391 gbeauche 1.6 pthread_mutex_unlock(&wakeup_time_lock);
392 gbeauche 1.4 timer_thread_resume();
393     assert(suspend_count == 0);
394     #endif
395 cebix 1.1
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 asvitkine 1.9 // 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 asvitkine 1.10 // 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 cebix 1.1
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 gbeauche 1.4 #if PRECISE_TIMING_BEOS
459 cebix 1.1 while (acquire_sem(wakeup_time_sem) == B_INTERRUPTED) ;
460     suspend_thread(timer_thread);
461     #endif
462 gbeauche 1.4 #if PRECISE_TIMING_POSIX
463     timer_thread_suspend();
464 gbeauche 1.6 pthread_mutex_lock(&wakeup_time_lock);
465 gbeauche 1.4 #endif
466 cebix 1.1 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 gbeauche 1.4 wakeup_time = wakeup_time_max;
471 cebix 1.1 for (int j=0; j<NUM_DESCS; j++) {
472     if (desc[j].in_use && (ReadMacInt16(desc[j].task + qType) & 0x8000))
473 gbeauche 1.4 if (timer_cmp_time(desc[j].wakeup, wakeup_time) < 0)
474 cebix 1.1 wakeup_time = desc[j].wakeup;
475     }
476 gbeauche 1.4 #ifdef PRECISE_TIMING_BEOS
477 cebix 1.1 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 gbeauche 1.4 #ifdef PRECISE_TIMING_POSIX
485 gbeauche 1.6 pthread_mutex_unlock(&wakeup_time_lock);
486 gbeauche 1.4 timer_thread_resume();
487     assert(suspend_count == 0);
488     #endif
489     #endif
490 cebix 1.1 return 0;
491     }
492    
493    
494     /*
495     * Time Manager thread
496     */
497    
498 gbeauche 1.4 #ifdef PRECISE_TIMING_BEOS
499 cebix 1.1 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 gbeauche 1.4 #ifdef PRECISE_TIMING_POSIX
521     static void *timer_func(void *arg)
522     {
523 gbeauche 1.5 while (!timer_thread_cancel) {
524 gbeauche 1.4
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 gbeauche 1.7 pthread_mutex_lock(&wakeup_time_lock);
534 gbeauche 1.4 wakeup_time = wakeup_time_max;
535 gbeauche 1.7 pthread_mutex_unlock(&wakeup_time_lock);
536 gbeauche 1.4 SetInterruptFlag(INTFLAG_TIMER);
537     TriggerInterrupt();
538     }
539     }
540     return NULL;
541     }
542     #endif
543    
544 cebix 1.1
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 gbeauche 1.4 #if PRECISE_TIMING_BEOS
581 cebix 1.1 while (acquire_sem(wakeup_time_sem) == B_INTERRUPTED) ;
582     suspend_thread(timer_thread);
583 gbeauche 1.4 #endif
584     #if PRECISE_TIMING_POSIX
585     timer_thread_suspend();
586 gbeauche 1.6 pthread_mutex_lock(&wakeup_time_lock);
587 gbeauche 1.4 #endif
588     wakeup_time = wakeup_time_max;
589 cebix 1.1 for (int j=0; j<NUM_DESCS; j++) {
590     if (desc[j].in_use && (ReadMacInt16(desc[j].task + qType) & 0x8000))
591 gbeauche 1.4 if (timer_cmp_time(desc[j].wakeup, wakeup_time) < 0)
592 cebix 1.1 wakeup_time = desc[j].wakeup;
593     }
594 gbeauche 1.4 #if PRECISE_TIMING_BEOS
595 cebix 1.1 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 gbeauche 1.4 #if PRECISE_TIMING_POSIX
603 gbeauche 1.6 pthread_mutex_unlock(&wakeup_time_lock);
604 gbeauche 1.4 timer_thread_resume();
605     assert(suspend_count == 0);
606     #endif
607     #endif
608 cebix 1.1 }