ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/cebix/BasiliskII/src/Unix/timer_unix.cpp
Revision: 1.21
Committed: 2009-08-17T20:42:26Z (15 years, 3 months ago) by asvitkine
Branch: MAIN
Changes since 1.20: +33 -6 lines
Log Message:
[Charles Srstka]
Attached is a set of patches to port the precise timer that is currently used in the Linux and BeOS builds of SheepShaver to Mac OS X (and any other Mach-based operating systems).

Currently, the Linux build uses the clock_gettime() function to get nanosecond-precision time, and falls back on gettimeofday() if it is not present. Unfortunately, Mac OS X does not currently support clock_gettime(), and gettimeofday() has only microsecond granularity. The Mach kernel, however, has a clock_get_time() function that does very nearly the same thing as clock_gettime(). The patches to BasiliskII cause the timing functions such as timer_current_time() to use clock_get_time() instead of gettimeofday() on Mach-based systems that do not support clock_gettime().

The changes to SheepShaver involve the precise timer. The existing code for Linux uses pthreads and real-time signals to handle the timing. Mac OS X unfortunately does not seem to support real-time signals, so Mach calls are again used to suspend and resume the timer thread in order to attempt to duplicate the Linux and BeOS versions of the timer. The code is somewhat ugly right now, as I decided to leave alone the pre-existing style of the source file, which unfortunately involves #ifdefs scattered throughout the file and some duplication of code. A future patch may want to clean this up to separate out the OS-specific code and put it all together at the top of the file. However, for the time being, this seems to work.

This has not been extensively tested, because I have not been able to get my hands on a good test-case app for the classic Mac OS that would run inside the emulator and try out the timer. However, performance does seem to be better than with the pre-existing code, and nothing seems to have blown up as far as I can tell. I did find a game via a Google search -  Cap'n Magneto - that is known to have problems with Basilisk/SheepShaver's legacy 60 Hz timer, and the opening fade-to-color for this game appears to run much more smoothly with the precise timer code in place.

File Contents

# User Rev Content
1 cebix 1.1 /*
2     * timer_unix.cpp - Time Manager emulation, Unix specific stuff
3     *
4 gbeauche 1.20 * Basilisk II (C) 1997-2008 Christian Bauer
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 cebix 1.10 #include "macos_util.h"
23 cebix 1.1 #include "timer.h"
24    
25 cebix 1.11 #include <errno.h>
26    
27 cebix 1.1 #define DEBUG 0
28     #include "debug.h"
29    
30 cebix 1.8 // For NetBSD with broken pthreads headers
31     #ifndef CLOCK_REALTIME
32     #define CLOCK_REALTIME 0
33     #endif
34    
35 asvitkine 1.21 #if defined(__MACH__)
36     #include <mach/mach_host.h>
37     #include <mach/clock.h>
38    
39     static clock_serv_t host_clock;
40     static bool host_clock_inited = false;
41    
42     static inline void mach_current_time(tm_time_t &t) {
43     if(!host_clock_inited) {
44     host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &host_clock);
45     host_clock_inited = true;
46     }
47    
48     clock_get_time(host_clock, &t);
49     }
50     #endif
51    
52 cebix 1.1
53     /*
54     * Return microseconds since boot (64 bit)
55     */
56    
57     void Microseconds(uint32 &hi, uint32 &lo)
58     {
59     D(bug("Microseconds\n"));
60 asvitkine 1.21 #if defined(HAVE_CLOCK_GETTIME)
61 cebix 1.1 struct timespec t;
62     clock_gettime(CLOCK_REALTIME, &t);
63     uint64 tl = (uint64)t.tv_sec * 1000000 + t.tv_nsec / 1000;
64 asvitkine 1.21 #elif defined(__MACH__)
65     tm_time_t t;
66     mach_current_time(t);
67     uint64 tl = (uint64)t.tv_sec * 1000000 + t.tv_nsec / 1000;
68 cebix 1.1 #else
69     struct timeval t;
70     gettimeofday(&t, NULL);
71     uint64 tl = (uint64)t.tv_sec * 1000000 + t.tv_usec;
72     #endif
73     hi = tl >> 32;
74     lo = tl;
75     }
76    
77    
78     /*
79     * Return local date/time in Mac format (seconds since 1.1.1904)
80     */
81    
82     uint32 TimerDateTime(void)
83     {
84 cebix 1.10 return TimeToMacTime(time(NULL));
85 cebix 1.1 }
86    
87    
88     /*
89     * Get current time
90     */
91    
92     void timer_current_time(tm_time_t &t)
93     {
94     #ifdef HAVE_CLOCK_GETTIME
95     clock_gettime(CLOCK_REALTIME, &t);
96 asvitkine 1.21 #elif defined(__MACH__)
97     mach_current_time(t);
98 cebix 1.1 #else
99     gettimeofday(&t, NULL);
100     #endif
101     }
102    
103    
104     /*
105     * Add times
106     */
107    
108     void timer_add_time(tm_time_t &res, tm_time_t a, tm_time_t b)
109     {
110 asvitkine 1.21 #if defined(HAVE_CLOCK_GETTIME) || defined(__MACH__)
111 cebix 1.1 res.tv_sec = a.tv_sec + b.tv_sec;
112     res.tv_nsec = a.tv_nsec + b.tv_nsec;
113     if (res.tv_nsec >= 1000000000) {
114     res.tv_sec++;
115     res.tv_nsec -= 1000000000;
116     }
117     #else
118     res.tv_sec = a.tv_sec + b.tv_sec;
119     res.tv_usec = a.tv_usec + b.tv_usec;
120     if (res.tv_usec >= 1000000) {
121     res.tv_sec++;
122     res.tv_usec -= 1000000;
123     }
124     #endif
125     }
126    
127    
128     /*
129     * Subtract times
130     */
131    
132     void timer_sub_time(tm_time_t &res, tm_time_t a, tm_time_t b)
133     {
134 asvitkine 1.21 #if defined(HAVE_CLOCK_GETTIME) || defined(__MACH__)
135 cebix 1.1 res.tv_sec = a.tv_sec - b.tv_sec;
136     res.tv_nsec = a.tv_nsec - b.tv_nsec;
137     if (res.tv_nsec < 0) {
138     res.tv_sec--;
139     res.tv_nsec += 1000000000;
140     }
141     #else
142     res.tv_sec = a.tv_sec - b.tv_sec;
143     res.tv_usec = a.tv_usec - b.tv_usec;
144     if (res.tv_usec < 0) {
145     res.tv_sec--;
146     res.tv_usec += 1000000;
147     }
148     #endif
149     }
150    
151    
152     /*
153     * Compare times (<0: a < b, =0: a = b, >0: a > b)
154     */
155    
156     int timer_cmp_time(tm_time_t a, tm_time_t b)
157     {
158 asvitkine 1.21 #if defined(HAVE_CLOCK_GETTIME) || defined(__MACH__)
159 cebix 1.1 if (a.tv_sec == b.tv_sec)
160     return a.tv_nsec - b.tv_nsec;
161     else
162     return a.tv_sec - b.tv_sec;
163     #else
164     if (a.tv_sec == b.tv_sec)
165     return a.tv_usec - b.tv_usec;
166     else
167     return a.tv_sec - b.tv_sec;
168     #endif
169     }
170    
171    
172     /*
173     * Convert Mac time value (>0: microseconds, <0: microseconds) to tm_time_t
174     */
175    
176     void timer_mac2host_time(tm_time_t &res, int32 mactime)
177     {
178 asvitkine 1.21 #if defined(HAVE_CLOCK_GETTIME) || defined(__MACH__)
179 cebix 1.1 if (mactime > 0) {
180     // Time in milliseconds
181     res.tv_sec = mactime / 1000;
182     res.tv_nsec = (mactime % 1000) * 1000000;
183     } else {
184     // Time in negative microseconds
185     res.tv_sec = -mactime / 1000000;
186     res.tv_nsec = (-mactime % 1000000) * 1000;
187     }
188     #else
189     if (mactime > 0) {
190     // Time in milliseconds
191     res.tv_sec = mactime / 1000;
192     res.tv_usec = (mactime % 1000) * 1000;
193     } else {
194     // Time in negative microseconds
195     res.tv_sec = -mactime / 1000000;
196     res.tv_usec = -mactime % 1000000;
197     }
198     #endif
199     }
200    
201    
202     /*
203     * Convert positive tm_time_t to Mac time value (>0: microseconds, <0: microseconds)
204     * A negative input value for hosttime results in a zero return value
205     * As long as the microseconds value fits in 32 bit, it must not be converted to milliseconds!
206     */
207    
208     int32 timer_host2mac_time(tm_time_t hosttime)
209     {
210     if (hosttime.tv_sec < 0)
211     return 0;
212     else {
213 asvitkine 1.21 #if defined(HAVE_CLOCK_GETTIME) || defined(__MACH__)
214 cebix 1.1 uint64 t = (uint64)hosttime.tv_sec * 1000000 + hosttime.tv_nsec / 1000;
215     #else
216     uint64 t = (uint64)hosttime.tv_sec * 1000000 + hosttime.tv_usec;
217     #endif
218     if (t > 0x7fffffff)
219     return t / 1000; // Time in milliseconds
220     else
221     return -t; // Time in negative microseconds
222     }
223 cebix 1.11 }
224    
225    
226     /*
227     * Get current value of microsecond timer
228     */
229    
230     uint64 GetTicks_usec(void)
231     {
232     #ifdef HAVE_CLOCK_GETTIME
233     struct timespec t;
234     clock_gettime(CLOCK_REALTIME, &t);
235     return (uint64)t.tv_sec * 1000000 + t.tv_nsec / 1000;
236 asvitkine 1.21 #elif defined(__MACH__)
237     tm_time_t t;
238     mach_current_time(t);
239     return (uint64)t.tv_sec * 1000000 + t.tv_nsec / 1000;
240 cebix 1.11 #else
241     struct timeval t;
242     gettimeofday(&t, NULL);
243     return (uint64)t.tv_sec * 1000000 + t.tv_usec;
244     #endif
245     }
246    
247    
248     /*
249     * Delay by specified number of microseconds (<1 second)
250     * (adapted from SDL_Delay() source; this function is designed to provide
251     * the highest accuracy possible)
252     */
253    
254     #if defined(linux)
255     // Linux select() changes its timeout parameter upon return to contain
256     // the remaining time. Most other unixen leave it unchanged or undefined.
257     #define SELECT_SETS_REMAINING
258 gbeauche 1.16 #elif defined(__FreeBSD__) || defined(__sun__) || (defined(__MACH__) && defined(__APPLE__))
259 cebix 1.11 #define USE_NANOSLEEP
260     #elif defined(HAVE_PTHREADS) && defined(sgi)
261     // SGI pthreads has a bug when using pthreads+signals+nanosleep,
262     // so instead of using nanosleep, wait on a CV which is never signalled.
263 cebix 1.12 #include <pthread.h>
264 cebix 1.11 #define USE_COND_TIMEDWAIT
265     #endif
266    
267     void Delay_usec(uint32 usec)
268     {
269     int was_error;
270    
271     #if defined(USE_NANOSLEEP)
272     struct timespec elapsed, tv;
273     #elif defined(USE_COND_TIMEDWAIT)
274     // Use a local mutex and cv, so threads remain independent
275     pthread_cond_t delay_cond = PTHREAD_COND_INITIALIZER;
276     pthread_mutex_t delay_mutex = PTHREAD_MUTEX_INITIALIZER;
277     struct timespec elapsed;
278     uint64 future;
279     #else
280     struct timeval tv;
281     #ifndef SELECT_SETS_REMAINING
282     uint64 then, now, elapsed;
283     #endif
284     #endif
285    
286     // Set the timeout interval - Linux only needs to do this once
287     #if defined(SELECT_SETS_REMAINING)
288     tv.tv_sec = 0;
289     tv.tv_usec = usec;
290     #elif defined(USE_NANOSLEEP)
291     elapsed.tv_sec = 0;
292     elapsed.tv_nsec = usec * 1000;
293     #elif defined(USE_COND_TIMEDWAIT)
294     future = GetTicks_usec() + usec;
295     elapsed.tv_sec = future / 1000000;
296     elapsed.tv_nsec = (future % 1000000) * 1000;
297     #else
298     then = GetTicks_usec();
299     #endif
300    
301     do {
302     errno = 0;
303     #if defined(USE_NANOSLEEP)
304     tv.tv_sec = elapsed.tv_sec;
305     tv.tv_nsec = elapsed.tv_nsec;
306     was_error = nanosleep(&tv, &elapsed);
307     #elif defined(USE_COND_TIMEDWAIT)
308     was_error = pthread_mutex_lock(&delay_mutex);
309     was_error = pthread_cond_timedwait(&delay_cond, &delay_mutex, &elapsed);
310     was_error = pthread_mutex_unlock(&delay_mutex);
311     #else
312     #ifndef SELECT_SETS_REMAINING
313     // Calculate the time interval left (in case of interrupt)
314     now = GetTicks_usec();
315     elapsed = now - then;
316     then = now;
317     if (elapsed >= usec)
318     break;
319     usec -= elapsed;
320     tv.tv_sec = 0;
321     tv.tv_usec = usec;
322     #endif
323     was_error = select(0, NULL, NULL, NULL, &tv);
324     #endif
325     } while (was_error && (errno == EINTR));
326 cebix 1.1 }
327 gbeauche 1.17
328    
329     /*
330     * Suspend emulator thread, virtual CPU in idle mode
331     */
332    
333     #ifdef HAVE_PTHREADS
334     #if defined(HAVE_PTHREAD_COND_INIT)
335     #define IDLE_USES_COND_WAIT 1
336     static pthread_mutex_t idle_lock = PTHREAD_MUTEX_INITIALIZER;
337     static pthread_cond_t idle_cond = PTHREAD_COND_INITIALIZER;
338     #elif defined(HAVE_SEM_INIT)
339     #define IDLE_USES_SEMAPHORE 1
340     #include <semaphore.h>
341     #ifdef HAVE_SPINLOCKS
342     static spinlock_t idle_lock = SPIN_LOCK_UNLOCKED;
343     #define LOCK_IDLE spin_lock(&idle_lock)
344     #define UNLOCK_IDLE spin_unlock(&idle_lock)
345     #else
346     static pthread_mutex_t idle_lock = PTHREAD_MUTEX_INITIALIZER;
347     #define LOCK_IDLE pthread_mutex_lock(&idle_lock)
348     #define UNLOCK_IDLE pthread_mutex_unlock(&idle_lock)
349     #endif
350     static sem_t idle_sem;
351     static int idle_sem_ok = -1;
352     #endif
353     #endif
354    
355     void idle_wait(void)
356     {
357     #ifdef IDLE_USES_COND_WAIT
358 gbeauche 1.18 pthread_mutex_lock(&idle_lock);
359 gbeauche 1.17 pthread_cond_wait(&idle_cond, &idle_lock);
360 gbeauche 1.18 pthread_mutex_unlock(&idle_lock);
361 gbeauche 1.17 #else
362     #ifdef IDLE_USES_SEMAPHORE
363 gbeauche 1.19 LOCK_IDLE;
364 gbeauche 1.17 if (idle_sem_ok < 0)
365     idle_sem_ok = (sem_init(&idle_sem, 0, 0) == 0);
366     if (idle_sem_ok > 0) {
367     idle_sem_ok++;
368     UNLOCK_IDLE;
369     sem_wait(&idle_sem);
370     return;
371     }
372 gbeauche 1.19 UNLOCK_IDLE;
373 gbeauche 1.17 #endif
374 gbeauche 1.19
375     // Fallback: sleep 10 ms
376 gbeauche 1.17 Delay_usec(10000);
377     #endif
378     }
379    
380    
381     /*
382     * Resume execution of emulator thread, events just arrived
383     */
384    
385     void idle_resume(void)
386     {
387     #ifdef IDLE_USES_COND_WAIT
388     pthread_cond_signal(&idle_cond);
389     #else
390     #ifdef IDLE_USES_SEMAPHORE
391 gbeauche 1.19 LOCK_IDLE;
392 gbeauche 1.17 if (idle_sem_ok > 1) {
393     idle_sem_ok--;
394     UNLOCK_IDLE;
395     sem_post(&idle_sem);
396     return;
397     }
398 gbeauche 1.19 UNLOCK_IDLE;
399 gbeauche 1.17 #endif
400     #endif
401     }