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