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

# Content
1 /*
2 * timer_unix.cpp - Time Manager emulation, Unix specific stuff
3 *
4 * Basilisk II (C) 1997-2008 Christian Bauer
5 *
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 "macos_util.h"
23 #include "timer.h"
24
25 #include <errno.h>
26
27 #define DEBUG 0
28 #include "debug.h"
29
30 // For NetBSD with broken pthreads headers
31 #ifndef CLOCK_REALTIME
32 #define CLOCK_REALTIME 0
33 #endif
34
35 #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
53 /*
54 * Return microseconds since boot (64 bit)
55 */
56
57 void Microseconds(uint32 &hi, uint32 &lo)
58 {
59 D(bug("Microseconds\n"));
60 #if defined(HAVE_CLOCK_GETTIME)
61 struct timespec t;
62 clock_gettime(CLOCK_REALTIME, &t);
63 uint64 tl = (uint64)t.tv_sec * 1000000 + t.tv_nsec / 1000;
64 #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 #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 return TimeToMacTime(time(NULL));
85 }
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 #elif defined(__MACH__)
97 mach_current_time(t);
98 #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 #if defined(HAVE_CLOCK_GETTIME) || defined(__MACH__)
111 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 #if defined(HAVE_CLOCK_GETTIME) || defined(__MACH__)
135 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 #if defined(HAVE_CLOCK_GETTIME) || defined(__MACH__)
159 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 #if defined(HAVE_CLOCK_GETTIME) || defined(__MACH__)
179 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 #if defined(HAVE_CLOCK_GETTIME) || defined(__MACH__)
214 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 }
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 #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 #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 #elif defined(__FreeBSD__) || defined(__sun__) || (defined(__MACH__) && defined(__APPLE__))
259 #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 #include <pthread.h>
264 #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 }
327
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 pthread_mutex_lock(&idle_lock);
359 pthread_cond_wait(&idle_cond, &idle_lock);
360 pthread_mutex_unlock(&idle_lock);
361 #else
362 #ifdef IDLE_USES_SEMAPHORE
363 LOCK_IDLE;
364 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 UNLOCK_IDLE;
373 #endif
374
375 // Fallback: sleep 10 ms
376 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 LOCK_IDLE;
392 if (idle_sem_ok > 1) {
393 idle_sem_ok--;
394 UNLOCK_IDLE;
395 sem_post(&idle_sem);
396 return;
397 }
398 UNLOCK_IDLE;
399 #endif
400 #endif
401 }