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() */
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 */
16 #include <CodeFragments.h>
17 #include <DriverServices.h>
20 #include "FastTimes.h"
23 #undef GENERATINGPOWERPC /* stop whining */
24 #define GENERATINGPOWERPC TARGET_CPU_PPC
27 /* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
28 /* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
30 On 680x0 machines, we just use Microseconds().
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.
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.
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.
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.
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.
67 /* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
68 /* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
70 #define RTCToNano(w) ((double) (w).hi * 1000000000.0 + (double) (w).lo)
71 #define WideTo64bit(w) (*(UInt64 *) &(w))
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)
81 static asm UnsignedWide PollRTC(void);
82 static asm UnsignedWide PollTBR(void);
83 static Ptr FindFunctionInSharedLib(StringPtr libName, StringPtr funcName);
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) */
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;
98 #endif /* GENERATINGPOWERPC */
100 /* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
101 /* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
103 void FastInitialize() {
108 #if GENERATINGPOWERPC
110 /* Initialize the feature flags */
111 gNative = gUseRTC = gUseTBR = false;
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 */
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 */
126 /* gestaltNativeTimeMgr = 4 in some future version of the headers */
127 if (!Gestalt(gestaltTimeMgrVersion, &result) &&
128 (result > gestaltExtendedTimeMgr))
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. */
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;
143 /* Now calculate a scale factor to keep us accurate. */
144 if ((gUpTime && !gNative) || gUseRTC || gUseTBR) {
145 UInt64 tick, usec1, usec2;
148 /* Wait for the beginning of the very next tick */
149 for(tick = MyLMGetTicks() + 1; tick > MyLMGetTicks(); );
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);
156 /* Wait for the exact 60th tick to roll over */
157 while(tick + 60 > MyLMGetTicks());
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);
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;
170 #endif /* GENERATINGPOWERPC */
172 /* We've initialized our globals */
177 /* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
178 /* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
180 UInt64 FastMicroseconds() {
184 #if GENERATINGPOWERPC
185 /* Initialize globals the first time we are called */
186 if (!gInited) FastInitialize();
189 /* Use DriverServices if it's available -- it's fast and compatible */
190 wide = (*gA2NS)((*gUpTime)());
191 usec = (double) WideTo64bit(wide) * gScaleUSec + 0.5;
194 /* Use DriverServices if it's available -- it's fast and compatible */
195 wide = (*gA2NS)((*gUpTime)());
196 usec = (double) WideTo64bit(wide) * gScaleUSec + 0.5;
199 /* On a recent PowerPC, we poll the TBR directly */
201 usec = (double) WideTo64bit(wide) * gScaleUSec + 0.5;
204 /* On a 601, we can poll the RTC instead */
206 usec = (double) RTCToNano(wide) * gScaleUSec + 0.5;
209 #endif /* GENERATINGPOWERPC */
211 /* If all else fails, suffer the mixed mode overhead */
213 usec = WideTo64bit(wide);
219 /* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
220 /* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
222 UInt64 FastMilliseconds() {
226 #if GENERATINGPOWERPC
227 /* Initialize globals the first time we are called */
228 if (!gInited) FastInitialize();
231 /* Use DriverServices if it's available -- it's fast and compatible */
232 wide = (*gA2NS)((*gUpTime)());
233 msec = (double) WideTo64bit(wide) * gScaleMSec + 0.5;
236 /* Use DriverServices if it's available -- it's fast and compatible */
237 wide = (*gA2NS)((*gUpTime)());
238 msec = (double) WideTo64bit(wide) * gScaleMSec + 0.5;
241 /* On a recent PowerPC, we poll the TBR directly */
243 msec = (double) WideTo64bit(wide) * gScaleMSec + 0.5;
246 /* On a 601, we can poll the RTC instead */
248 msec = (double) RTCToNano(wide) * gScaleMSec + 0.5;
251 #endif /* GENERATINGPOWERPC */
253 /* If all else fails, suffer the mixed mode overhead */
255 msec = ((double) WideTo64bit(wide) + 500.0) / 1000.0;
261 /* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
262 /* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
264 StringPtr FastMethod() {
265 StringPtr method = "\p<Unknown>";
267 #if GENERATINGPOWERPC
268 /* Initialize globals the first time we are called */
269 if (!gInited) FastInitialize();
272 /* The Time Manager and UpTime() are entirely native on this machine */
273 method = "\pNative UpTime()";
276 /* Use DriverServices if it's available -- it's fast and compatible */
277 method = "\pUpTime()";
280 /* On a recent PowerPC, we poll the TBR directly */
281 method = "\pPowerPC TBR";
284 /* On a 601, we can poll the RTC instead */
285 method = "\pPowerPC RTC";
288 #endif /* GENERATINGPOWERPC */
290 /* If all else fails, suffer the mixed mode overhead */
291 method = "\pMicroseconds()";
297 /* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
298 /* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
301 #if GENERATINGPOWERPC
302 asm static UnsignedWide PollRTC_() {
303 entry PollRTC /* Avoid CodeWarrior glue */
306 mfrtcu r4 /* RTCU = SPR 4 */
307 mfrtcl r5 /* RTCL = SPR 5 */
316 /* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
317 /* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
319 asm static UnsignedWide PollTBR_() {
320 entry PollTBR /* Avoid CodeWarrior glue */
323 mftbu r4 /* TBRU = SPR 268 */
324 mftb r5 /* TBRL = SPR 269 */
333 /* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
334 /* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
336 static Ptr FindFunctionInSharedLib(StringPtr libName, StringPtr funcName) {
341 CFragSymbolClass symClass;
342 CFragConnectionID connID;
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))
352 #endif /* GENERATINGPOWERPC */