0
|
1 /* File "FastTimes.c" - Original code by Matt Slot <fprefect@ambrosiasw.com> */
|
|
2 /* Created 4/24/99 - This file is hereby placed in the public domain */
|
|
3 /* Updated 5/21/99 - Calibrate to VIA, add TBR support, renamed functions */
|
|
4 /* Updated 10/4/99 - Use AbsoluteToNanoseconds() in case Absolute = double */
|
|
5 /* Updated 2/15/00 - Check for native Time Manager, no need to calibrate */
|
|
6 /* Updated 2/19/00 - Fixed default value for gScale under native Time Mgr */
|
|
7 /* Updated 3/21/00 - Fixed ns conversion, create 2 different scale factors */
|
|
8 /* Updated 5/03/00 - Added copyright and placed into PD. No code changes */
|
|
9 /* Updated 8/01/00 - Made "Carbon-compatible" by replacing LMGetTicks() */
|
|
10
|
|
11 /* This file is Copyright (C) Matt Slot, 1999-2000. It is hereby placed into
|
|
12 the public domain. The author makes no warranty as to fitness or stability */
|
|
13
|
|
14 #include <Gestalt.h>
|
|
15 #include <LowMem.h>
|
|
16 #include <CodeFragments.h>
|
|
17 #include <DriverServices.h>
|
|
18 #include <Timer.h>
|
|
19
|
|
20 #include "FastTimes.h"
|
|
21
|
|
22 /* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
|
|
23 /* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
|
|
24 /*
|
|
25 On 680x0 machines, we just use Microseconds().
|
|
26
|
|
27 On PowerPC machines, we try several methods:
|
|
28 * DriverServicesLib is available on all PCI PowerMacs, and perhaps
|
|
29 some NuBus PowerMacs. If it is, we use UpTime() : Overhead = 2.1 µsec.
|
|
30 * The PowerPC 601 has a built-in "real time clock" RTC, and we fall
|
|
31 back to that, accessing it directly from asm. Overhead = 1.3 µsec.
|
|
32 * Later PowerPCs have an accurate "time base register" TBR, and we
|
|
33 fall back to that, access it from PowerPC asm. Overhead = 1.3 µsec.
|
|
34 * We can also try Microseconds() which is emulated : Overhead = 36 µsec.
|
|
35
|
|
36 On PowerPC machines, we avoid the following:
|
|
37 * OpenTransport is available on all PCI and some NuBus PowerMacs, but it
|
|
38 uses UpTime() if available and falls back to Microseconds() otherwise.
|
|
39 * InputSprocket is available on many PowerMacs, but again it uses
|
|
40 UpTime() if available and falls back to Microseconds() otherwise.
|
|
41
|
|
42 Another PowerPC note: certain configurations, especially 3rd party upgrade
|
|
43 cards, may return inaccurate timings for the CPU or memory bus -- causing
|
|
44 skew in various system routines (up to 20% drift!). The VIA chip is very
|
|
45 accurate, and it's the basis for the Time Manager and Microseconds().
|
|
46 Unfortunately, it's also very slow because the MacOS has to (a) switch to
|
|
47 68K and (b) poll for a VIA event.
|
|
48
|
|
49 We compensate for the drift by calibrating a floating point scale factor
|
|
50 between our fast method and the accurate timer at startup, then convert
|
|
51 each sample quickly on the fly. I'd rather not have the initialization
|
|
52 overhead -- but it's simply necessary for accurate timing. You can drop
|
|
53 it down to 30 ticks if you prefer, but that's as low as I'd recommend.
|
|
54
|
|
55 Under MacOS 9, "new world" Macs (iMacs, B+W G3s and G+W G4s) have a native
|
|
56 Time Manager implementation: UpTime(), Microseconds(), and TickCount() are
|
|
57 all based on the same underlying counter. This makes it silly to calibrate
|
|
58 UpTime() against TickCount(). We now check for this feature using Gestalt(),
|
|
59 and skip the whole calibration step if possible.
|
|
60
|
|
61 */
|
|
62 /* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
|
|
63 /* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
|
|
64
|
|
65 #define RTCToNano(w) ((double) (w).hi * 1000000000.0 + (double) (w).lo)
|
|
66 #define WideTo64bit(w) (*(UInt64 *) &(w))
|
|
67
|
|
68 /* LMGetTicks() is not in Carbon and TickCount() has a fair bit of overhead,
|
|
69 so for speed we always read lowmem directly. This is a MacOS X no-no, but
|
|
70 it always work on those systems that don't have a native Time Manager (ie,
|
|
71 anything before MacOS 9) -- regardless whether we are in Carbon or not! */
|
|
72 #define MyLMGetTicks() (*(volatile UInt32 *) 0x16A)
|
|
73
|
|
74 #if GENERATINGPOWERPC
|
|
75
|
|
76 static asm UnsignedWide PollRTC(void);
|
|
77 static asm UnsignedWide PollTBR(void);
|
|
78 static Ptr FindFunctionInSharedLib(StringPtr libName, StringPtr funcName);
|
|
79
|
|
80 static Boolean gInited = false;
|
|
81 static Boolean gNative = false;
|
|
82 static Boolean gUseRTC = false;
|
|
83 static Boolean gUseTBR = false;
|
|
84 static double gScaleUSec = 1.0 / 1000.0; /* 1 / ( nsec / usec) */
|
|
85 static double gScaleMSec = 1.0 / 1000000.0; /* 1 / ( nsec / msec) */
|
|
86
|
|
87 /* Functions loaded from DriverServicesLib */
|
|
88 typedef AbsoluteTime (*UpTimeProcPtr)(void);
|
|
89 typedef Nanoseconds (*A2NSProcPtr)(AbsoluteTime);
|
|
90 static UpTimeProcPtr gUpTime = NULL;
|
|
91 static A2NSProcPtr gA2NS = NULL;
|
|
92
|
|
93 #endif /* GENERATINGPOWERPC */
|
|
94
|
|
95 /* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
|
|
96 /* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
|
|
97
|
|
98 void FastInitialize() {
|
|
99 SInt32 result;
|
|
100
|
|
101 if (!gInited) {
|
|
102
|
|
103 #if GENERATINGPOWERPC
|
|
104
|
|
105 /* Initialize the feature flags */
|
|
106 gNative = gUseRTC = gUseTBR = false;
|
|
107
|
|
108 /* We use CFM to find and load needed symbols from shared libraries, so
|
|
109 the application doesn't have to weak-link them, for convenience. */
|
|
110 gUpTime = (UpTimeProcPtr) FindFunctionInSharedLib(
|
|
111 "\pDriverServicesLib", "\pUpTime");
|
|
112 if (gUpTime) gA2NS = (A2NSProcPtr) FindFunctionInSharedLib(
|
|
113 "\pDriverServicesLib", "\pAbsoluteToNanoseconds");
|
|
114 if (!gA2NS) gUpTime = nil; /* Pedantic but necessary */
|
|
115
|
|
116 if (gUpTime) {
|
|
117 /* If we loaded UpTime(), then we need to know if the system has
|
|
118 a native implementation of the Time Manager. If so, then it's
|
|
119 pointless to calculate a scale factor against the missing VIA */
|
|
120
|
|
121 /* gestaltNativeTimeMgr = 4 in some future version of the headers */
|
|
122 if (!Gestalt(gestaltTimeMgrVersion, &result) &&
|
|
123 (result > gestaltExtendedTimeMgr))
|
|
124 gNative = true;
|
|
125 }
|
|
126 else {
|
|
127 /* If no DriverServicesLib, use Gestalt() to get the processor type.
|
|
128 Only NuBus PowerMacs with old System Software won't have DSL, so
|
|
129 we know it should either be a 601 or 603. */
|
|
130
|
|
131 /* Use the processor gestalt to determine which register to use */
|
|
132 if (!Gestalt(gestaltNativeCPUtype, &result)) {
|
|
133 if (result == gestaltCPU601) gUseRTC = true;
|
|
134 else if (result > gestaltCPU601) gUseTBR = true;
|
|
135 }
|
|
136 }
|
|
137
|
|
138 /* Now calculate a scale factor to keep us accurate. */
|
|
139 if ((gUpTime && !gNative) || gUseRTC || gUseTBR) {
|
|
140 UInt64 tick, usec1, usec2;
|
|
141 UnsignedWide wide;
|
|
142
|
|
143 /* Wait for the beginning of the very next tick */
|
|
144 for(tick = MyLMGetTicks() + 1; tick > MyLMGetTicks(); );
|
|
145
|
|
146 /* Poll the selected timer and prepare it (since we have time) */
|
|
147 wide = (gUpTime) ? (*gA2NS)((*gUpTime)()) :
|
|
148 ((gUseRTC) ? PollRTC() : PollTBR());
|
|
149 usec1 = (gUseRTC) ? RTCToNano(wide) : WideTo64bit(wide);
|
|
150
|
|
151 /* Wait for the exact 60th tick to roll over */
|
|
152 while(tick + 60 > MyLMGetTicks());
|
|
153
|
|
154 /* Poll the selected timer again and prepare it */
|
|
155 wide = (gUpTime) ? (*gA2NS)((*gUpTime)()) :
|
|
156 ((gUseRTC) ? PollRTC() : PollTBR());
|
|
157 usec2 = (gUseRTC) ? RTCToNano(wide) : WideTo64bit(wide);
|
|
158
|
|
159 /* Calculate a scale value that will give microseconds per second.
|
|
160 Remember, there are actually 60.15 ticks in a second, not 60. */
|
|
161 gScaleUSec = (60.0 * 1000000.0) / ((usec2 - usec1) * 60.15);
|
|
162 gScaleMSec = gScaleUSec / 1000.0;
|
|
163 }
|
|
164
|
|
165 #endif /* GENERATINGPOWERPC */
|
|
166
|
|
167 /* We've initialized our globals */
|
|
168 gInited = true;
|
|
169 }
|
|
170 }
|
|
171
|
|
172 /* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
|
|
173 /* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
|
|
174
|
|
175 UInt64 FastMicroseconds() {
|
|
176 UnsignedWide wide;
|
|
177 UInt64 usec;
|
|
178
|
|
179 #if GENERATINGPOWERPC
|
|
180 /* Initialize globals the first time we are called */
|
|
181 if (!gInited) FastInitialize();
|
|
182
|
|
183 if (gNative) {
|
|
184 /* Use DriverServices if it's available -- it's fast and compatible */
|
|
185 wide = (*gA2NS)((*gUpTime)());
|
|
186 usec = (double) WideTo64bit(wide) * gScaleUSec + 0.5;
|
|
187 }
|
|
188 else if (gUpTime) {
|
|
189 /* Use DriverServices if it's available -- it's fast and compatible */
|
|
190 wide = (*gA2NS)((*gUpTime)());
|
|
191 usec = (double) WideTo64bit(wide) * gScaleUSec + 0.5;
|
|
192 }
|
|
193 else if (gUseTBR) {
|
|
194 /* On a recent PowerPC, we poll the TBR directly */
|
|
195 wide = PollTBR();
|
|
196 usec = (double) WideTo64bit(wide) * gScaleUSec + 0.5;
|
|
197 }
|
|
198 else if (gUseRTC) {
|
|
199 /* On a 601, we can poll the RTC instead */
|
|
200 wide = PollRTC();
|
|
201 usec = (double) RTCToNano(wide) * gScaleUSec + 0.5;
|
|
202 }
|
|
203 else
|
|
204 #endif /* GENERATINGPOWERPC */
|
|
205 {
|
|
206 /* If all else fails, suffer the mixed mode overhead */
|
|
207 Microseconds(&wide);
|
|
208 usec = WideTo64bit(wide);
|
|
209 }
|
|
210
|
|
211 return(usec);
|
|
212 }
|
|
213
|
|
214 /* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
|
|
215 /* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
|
|
216
|
|
217 UInt64 FastMilliseconds() {
|
|
218 UnsignedWide wide;
|
|
219 UInt64 msec;
|
|
220
|
|
221 #if GENERATINGPOWERPC
|
|
222 /* Initialize globals the first time we are called */
|
|
223 if (!gInited) FastInitialize();
|
|
224
|
|
225 if (gNative) {
|
|
226 /* Use DriverServices if it's available -- it's fast and compatible */
|
|
227 wide = (*gA2NS)((*gUpTime)());
|
|
228 msec = (double) WideTo64bit(wide) * gScaleMSec + 0.5;
|
|
229 }
|
|
230 else if (gUpTime) {
|
|
231 /* Use DriverServices if it's available -- it's fast and compatible */
|
|
232 wide = (*gA2NS)((*gUpTime)());
|
|
233 msec = (double) WideTo64bit(wide) * gScaleMSec + 0.5;
|
|
234 }
|
|
235 else if (gUseTBR) {
|
|
236 /* On a recent PowerPC, we poll the TBR directly */
|
|
237 wide = PollTBR();
|
|
238 msec = (double) WideTo64bit(wide) * gScaleMSec + 0.5;
|
|
239 }
|
|
240 else if (gUseRTC) {
|
|
241 /* On a 601, we can poll the RTC instead */
|
|
242 wide = PollRTC();
|
|
243 msec = (double) RTCToNano(wide) * gScaleMSec + 0.5;
|
|
244 }
|
|
245 else
|
|
246 #endif /* GENERATINGPOWERPC */
|
|
247 {
|
|
248 /* If all else fails, suffer the mixed mode overhead */
|
|
249 Microseconds(&wide);
|
|
250 msec = ((double) WideTo64bit(wide) + 500.0) / 1000.0;
|
|
251 }
|
|
252
|
|
253 return(msec);
|
|
254 }
|
|
255
|
|
256 /* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
|
|
257 /* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
|
|
258
|
|
259 StringPtr FastMethod() {
|
|
260 StringPtr method = "\p<Unknown>";
|
|
261
|
|
262 #if GENERATINGPOWERPC
|
|
263 /* Initialize globals the first time we are called */
|
|
264 if (!gInited) FastInitialize();
|
|
265
|
|
266 if (gNative) {
|
|
267 /* The Time Manager and UpTime() are entirely native on this machine */
|
|
268 method = "\pNative UpTime()";
|
|
269 }
|
|
270 else if (gUpTime) {
|
|
271 /* Use DriverServices if it's available -- it's fast and compatible */
|
|
272 method = "\pUpTime()";
|
|
273 }
|
|
274 else if (gUseTBR) {
|
|
275 /* On a recent PowerPC, we poll the TBR directly */
|
|
276 method = "\pPowerPC TBR";
|
|
277 }
|
|
278 else if (gUseRTC) {
|
|
279 /* On a 601, we can poll the RTC instead */
|
|
280 method = "\pPowerPC RTC";
|
|
281 }
|
|
282 else
|
|
283 #endif /* GENERATINGPOWERPC */
|
|
284 {
|
|
285 /* If all else fails, suffer the mixed mode overhead */
|
|
286 method = "\pMicroseconds()";
|
|
287 }
|
|
288
|
|
289 return(method);
|
|
290 }
|
|
291
|
|
292 /* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
|
|
293 /* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
|
|
294 #pragma mark -
|
|
295
|
|
296 #if GENERATINGPOWERPC
|
|
297 asm static UnsignedWide PollRTC_() {
|
|
298 entry PollRTC /* Avoid CodeWarrior glue */
|
|
299 machine 601
|
|
300 @AGAIN:
|
|
301 mfrtcu r4 /* RTCU = SPR 4 */
|
|
302 mfrtcl r5 /* RTCL = SPR 5 */
|
|
303 mfrtcu r6
|
|
304 cmpw r4,r6
|
|
305 bne @AGAIN
|
|
306 stw r4,0(r3)
|
|
307 stw r5,4(r3)
|
|
308 blr
|
|
309 }
|
|
310
|
|
311 /* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
|
|
312 /* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
|
|
313
|
|
314 asm static UnsignedWide PollTBR_() {
|
|
315 entry PollTBR /* Avoid CodeWarrior glue */
|
|
316 machine 604
|
|
317 @AGAIN:
|
|
318 mftbu r4 /* TBRU = SPR 268 */
|
|
319 mftb r5 /* TBRL = SPR 269 */
|
|
320 mftbu r6
|
|
321 cmpw r4,r6
|
|
322 bne @AGAIN
|
|
323 stw r4,0(r3)
|
|
324 stw r5,4(r3)
|
|
325 blr
|
|
326 }
|
|
327
|
|
328 /* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
|
|
329 /* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
|
|
330
|
|
331 static Ptr FindFunctionInSharedLib(StringPtr libName, StringPtr funcName) {
|
|
332 OSErr error = noErr;
|
|
333 Str255 errorStr;
|
|
334 Ptr func = NULL;
|
|
335 Ptr entry = NULL;
|
|
336 CFragSymbolClass symClass;
|
|
337 CFragConnectionID connID;
|
|
338
|
|
339 /* Find CFM containers for the current archecture -- CFM-PPC or CFM-68K */
|
|
340 if (/* error = */ GetSharedLibrary(libName, kCompiledCFragArch,
|
|
341 kLoadCFrag, &connID, &entry, errorStr)) return(NULL);
|
|
342 if (/* error = */ FindSymbol(connID, funcName, &func, &symClass))
|
|
343 return(NULL);
|
|
344
|
|
345 return(func);
|
|
346 }
|
|
347 #endif /* GENERATINGPOWERPC */
|