Home | History | Annotate | Download | only in macos
      1 /* File "FastTimes.c" - Original code by Matt Slot <fprefect (at) 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 
    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 
    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 
    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 
    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
    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 
    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 
    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