diff src/timer/macos/FastTimes.c @ 0:74212992fb08

Initial revision
author Sam Lantinga <slouken@lokigames.com>
date Thu, 26 Apr 2001 16:45:43 +0000
parents
children f12379c41042
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/timer/macos/FastTimes.c	Thu Apr 26 16:45:43 2001 +0000
@@ -0,0 +1,347 @@
+/* File "FastTimes.c" - Original code by Matt Slot <fprefect@ambrosiasw.com>  */
+/* Created 4/24/99    - This file is hereby placed in the public domain       */
+/* Updated 5/21/99    - Calibrate to VIA, add TBR support, renamed functions  */
+/* Updated 10/4/99    - Use AbsoluteToNanoseconds() in case Absolute = double */
+/* Updated 2/15/00    - Check for native Time Manager, no need to calibrate   */
+/* Updated 2/19/00    - Fixed default value for gScale under native Time Mgr  */
+/* Updated 3/21/00    - Fixed ns conversion, create 2 different scale factors */
+/* Updated 5/03/00    - Added copyright and placed into PD. No code changes   */
+/* Updated 8/01/00    - Made "Carbon-compatible" by replacing LMGetTicks()    */
+
+/* This file is Copyright (C) Matt Slot, 1999-2000. It is hereby placed into 
+   the public domain. The author makes no warranty as to fitness or stability */
+
+#include <Gestalt.h>
+#include <LowMem.h>
+#include <CodeFragments.h>
+#include <DriverServices.h>
+#include <Timer.h>
+
+#include "FastTimes.h"
+
+/* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
+/* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
+/*
+	On 680x0 machines, we just use Microseconds().
+	
+	On PowerPC machines, we try several methods:
+	  * DriverServicesLib is available on all PCI PowerMacs, and perhaps
+	    some NuBus PowerMacs. If it is, we use UpTime() : Overhead = 2.1 µsec.
+	  * The PowerPC 601 has a built-in "real time clock" RTC, and we fall
+	    back to that, accessing it directly from asm. Overhead = 1.3 µsec.
+	  * Later PowerPCs have an accurate "time base register" TBR, and we 
+	    fall back to that, access it from PowerPC asm. Overhead = 1.3 µsec.
+	  * We can also try Microseconds() which is emulated : Overhead = 36 µsec.
+
+	On PowerPC machines, we avoid the following:
+	  * OpenTransport is available on all PCI and some NuBus PowerMacs, but it
+	    uses UpTime() if available and falls back to Microseconds() otherwise.
+	  * InputSprocket is available on many PowerMacs, but again it uses
+	    UpTime() if available and falls back to Microseconds() otherwise.
+
+	Another PowerPC note: certain configurations, especially 3rd party upgrade
+	cards, may return inaccurate timings for the CPU or memory bus -- causing
+	skew in various system routines (up to 20% drift!). The VIA chip is very
+	accurate, and it's the basis for the Time Manager and Microseconds().
+	Unfortunately, it's also very slow because the MacOS has to (a) switch to
+	68K and (b) poll for a VIA event.
+	
+	We compensate for the drift by calibrating a floating point scale factor
+	between our fast method and the accurate timer at startup, then convert
+	each sample quickly on the fly. I'd rather not have the initialization 
+	overhead -- but it's simply necessary for accurate timing. You can drop
+	it down to 30 ticks if you prefer, but that's as low as I'd recommend.
+
+	Under MacOS 9, "new world" Macs (iMacs, B+W G3s and G+W G4s) have a native
+	Time Manager implementation: UpTime(), Microseconds(), and TickCount() are
+	all based on the same underlying counter. This makes it silly to calibrate
+	UpTime() against TickCount(). We now check for this feature using Gestalt(),
+	and skip the whole calibration step if possible.
+
+*/
+/* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
+/* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
+
+#define RTCToNano(w)	((double) (w).hi * 1000000000.0 + (double) (w).lo)
+#define WideTo64bit(w)	(*(UInt64 *) &(w))
+
+/* LMGetTicks() is not in Carbon and TickCount() has a fair bit of overhead,
+   so for speed we always read lowmem directly. This is a MacOS X no-no, but 
+   it always work on those systems that don't have a native Time Manager (ie,
+   anything before MacOS 9) -- regardless whether we are in Carbon or not! */
+#define MyLMGetTicks()	(*(volatile UInt32 *) 0x16A)
+
+#if GENERATINGPOWERPC
+
+static asm UnsignedWide PollRTC(void);
+static asm UnsignedWide PollTBR(void);
+static Ptr FindFunctionInSharedLib(StringPtr libName, StringPtr funcName);
+
+static Boolean			gInited = false;
+static Boolean			gNative = false;
+static Boolean			gUseRTC = false;
+static Boolean			gUseTBR = false;
+static double			gScaleUSec = 1.0 / 1000.0;    /* 1 / ( nsec / usec) */
+static double			gScaleMSec = 1.0 / 1000000.0; /* 1 / ( nsec / msec) */
+
+/* Functions loaded from DriverServicesLib */
+typedef AbsoluteTime 	(*UpTimeProcPtr)(void);
+typedef Nanoseconds 	(*A2NSProcPtr)(AbsoluteTime);
+static UpTimeProcPtr 	gUpTime = NULL;
+static A2NSProcPtr 		gA2NS = NULL;
+
+#endif /* GENERATINGPOWERPC */
+
+/* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
+/* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
+
+void FastInitialize() {
+	SInt32			result;
+
+	if (!gInited) {
+
+#if GENERATINGPOWERPC
+
+		/* Initialize the feature flags */
+		gNative = gUseRTC = gUseTBR = false;
+
+		/* We use CFM to find and load needed symbols from shared libraries, so
+		   the application doesn't have to weak-link them, for convenience.   */
+		gUpTime = (UpTimeProcPtr) FindFunctionInSharedLib(
+				"\pDriverServicesLib", "\pUpTime");
+		if (gUpTime) gA2NS = (A2NSProcPtr) FindFunctionInSharedLib(
+				"\pDriverServicesLib", "\pAbsoluteToNanoseconds");
+		if (!gA2NS) gUpTime = nil; /* Pedantic but necessary */
+
+		if (gUpTime) {
+			/* If we loaded UpTime(), then we need to know if the system has
+			   a native implementation of the Time Manager. If so, then it's
+			   pointless to calculate a scale factor against the missing VIA */
+
+			/* gestaltNativeTimeMgr = 4 in some future version of the headers */
+			if (!Gestalt(gestaltTimeMgrVersion, &result) &&
+					(result > gestaltExtendedTimeMgr)) 
+				gNative = true;
+			}
+		  else {
+			/* If no DriverServicesLib, use Gestalt() to get the processor type. 
+			   Only NuBus PowerMacs with old System Software won't have DSL, so
+			   we know it should either be a 601 or 603. */
+
+			/* Use the processor gestalt to determine which register to use */
+		 	if (!Gestalt(gestaltNativeCPUtype, &result)) {
+				if (result == gestaltCPU601) gUseRTC = true;
+				  else if (result > gestaltCPU601) gUseTBR = true;
+				}
+			}
+
+		/* Now calculate a scale factor to keep us accurate. */
+		if ((gUpTime && !gNative) || gUseRTC || gUseTBR) {
+			UInt64			tick, usec1, usec2;
+			UnsignedWide	wide;
+
+			/* Wait for the beginning of the very next tick */
+			for(tick = MyLMGetTicks() + 1; tick > MyLMGetTicks(); );
+			
+			/* Poll the selected timer and prepare it (since we have time) */
+			wide = (gUpTime) ? (*gA2NS)((*gUpTime)()) : 
+					((gUseRTC) ? PollRTC() : PollTBR());
+			usec1 = (gUseRTC) ? RTCToNano(wide) : WideTo64bit(wide);
+			
+			/* Wait for the exact 60th tick to roll over */
+			while(tick + 60 > MyLMGetTicks());
+
+			/* Poll the selected timer again and prepare it  */
+			wide = (gUpTime) ? (*gA2NS)((*gUpTime)()) : 
+					((gUseRTC) ? PollRTC() : PollTBR());
+			usec2 = (gUseRTC) ? RTCToNano(wide) : WideTo64bit(wide);
+			
+			/* Calculate a scale value that will give microseconds per second.
+			   Remember, there are actually 60.15 ticks in a second, not 60.  */
+			gScaleUSec = (60.0 * 1000000.0) / ((usec2 - usec1) * 60.15);
+			gScaleMSec = gScaleUSec / 1000.0;
+			}
+
+#endif /* GENERATINGPOWERPC */
+
+		/* We've initialized our globals */
+		gInited = true;
+		}
+	}
+
+/* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
+/* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
+
+UInt64 FastMicroseconds() {
+	UnsignedWide	wide;
+	UInt64			usec;
+	
+#if GENERATINGPOWERPC
+	/* Initialize globals the first time we are called */
+	if (!gInited) FastInitialize();
+	
+	if (gNative) {
+		/* Use DriverServices if it's available -- it's fast and compatible */
+		wide = (*gA2NS)((*gUpTime)());
+		usec = (double) WideTo64bit(wide) * gScaleUSec + 0.5;
+		}
+	  else if (gUpTime) {
+		/* Use DriverServices if it's available -- it's fast and compatible */
+		wide = (*gA2NS)((*gUpTime)());
+		usec = (double) WideTo64bit(wide) * gScaleUSec + 0.5;
+		}
+	  else if (gUseTBR) {
+		/* On a recent PowerPC, we poll the TBR directly */
+		wide = PollTBR();
+		usec = (double) WideTo64bit(wide) * gScaleUSec + 0.5;
+		}
+	  else if (gUseRTC) {
+		/* On a 601, we can poll the RTC instead */
+		wide = PollRTC();
+		usec = (double) RTCToNano(wide) * gScaleUSec + 0.5;
+		}
+	  else 
+#endif /* GENERATINGPOWERPC */
+		{
+		/* If all else fails, suffer the mixed mode overhead */
+		Microseconds(&wide);
+		usec = WideTo64bit(wide);
+		}
+
+	return(usec);
+	}
+
+/* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
+/* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
+
+UInt64 FastMilliseconds() {
+	UnsignedWide	wide;
+	UInt64			msec;	
+	
+#if GENERATINGPOWERPC
+	/* Initialize globals the first time we are called */
+	if (!gInited) FastInitialize();
+	
+	if (gNative) {
+		/* Use DriverServices if it's available -- it's fast and compatible */
+		wide = (*gA2NS)((*gUpTime)());
+		msec = (double) WideTo64bit(wide) * gScaleMSec + 0.5;
+		}
+	  else if (gUpTime) {
+		/* Use DriverServices if it's available -- it's fast and compatible */
+		wide = (*gA2NS)((*gUpTime)());
+		msec = (double) WideTo64bit(wide) * gScaleMSec + 0.5;
+		}
+	  else if (gUseTBR) {
+		/* On a recent PowerPC, we poll the TBR directly */
+		wide = PollTBR();
+		msec = (double) WideTo64bit(wide) * gScaleMSec + 0.5;
+		}
+	  else if (gUseRTC) {
+		/* On a 601, we can poll the RTC instead */
+		wide = PollRTC();
+		msec = (double) RTCToNano(wide) * gScaleMSec + 0.5;
+		}
+	  else 
+#endif /* GENERATINGPOWERPC */
+		{
+		/* If all else fails, suffer the mixed mode overhead */
+		Microseconds(&wide);
+		msec = ((double) WideTo64bit(wide) + 500.0) / 1000.0;
+		}
+
+	return(msec);
+	}
+
+/* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
+/* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
+
+StringPtr FastMethod() {
+	StringPtr	method = "\p<Unknown>";
+
+#if GENERATINGPOWERPC
+	/* Initialize globals the first time we are called */
+	if (!gInited) FastInitialize();
+	
+	if (gNative) {
+		/* The Time Manager and UpTime() are entirely native on this machine */
+		method = "\pNative UpTime()";
+		}
+	  else if (gUpTime) {
+		/* Use DriverServices if it's available -- it's fast and compatible */
+		method = "\pUpTime()";
+		}
+	  else if (gUseTBR) {
+		/* On a recent PowerPC, we poll the TBR directly */
+		method = "\pPowerPC TBR";
+		}
+	  else if (gUseRTC) {
+		/* On a 601, we can poll the RTC instead */
+		method = "\pPowerPC RTC";
+		}
+	  else 
+#endif /* GENERATINGPOWERPC */
+		{
+		/* If all else fails, suffer the mixed mode overhead */
+		method = "\pMicroseconds()";
+		}
+
+	return(method);
+	}
+
+/* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
+/* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
+#pragma mark -
+
+#if GENERATINGPOWERPC
+asm static UnsignedWide PollRTC_() {
+entry PollRTC /* Avoid CodeWarrior glue */
+	machine 601
+@AGAIN:
+	mfrtcu	r4 /* RTCU = SPR 4 */
+	mfrtcl	r5 /* RTCL = SPR 5 */
+	mfrtcu	r6
+	cmpw	r4,r6
+	bne		@AGAIN
+	stw		r4,0(r3)
+	stw		r5,4(r3)
+	blr
+	}
+
+/* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
+/* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
+
+asm static UnsignedWide PollTBR_() {
+entry PollTBR /* Avoid CodeWarrior glue */
+	machine 604
+@AGAIN:
+	mftbu	r4 /* TBRU = SPR 268 */
+	mftb	r5 /* TBRL = SPR 269 */
+	mftbu	r6
+	cmpw	r4,r6
+	bne		@AGAIN
+	stw		r4,0(r3)
+	stw		r5,4(r3)
+	blr
+	}
+
+/* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
+/* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
+
+static Ptr FindFunctionInSharedLib(StringPtr libName, StringPtr funcName) {
+	OSErr				error = noErr;
+	Str255				errorStr;
+	Ptr					func = NULL;
+	Ptr					entry = NULL;
+	CFragSymbolClass	symClass;
+	CFragConnectionID	connID;
+	
+	/* Find CFM containers for the current archecture -- CFM-PPC or CFM-68K */
+	if (/* error = */ GetSharedLibrary(libName, kCompiledCFragArch,
+			kLoadCFrag, &connID, &entry, errorStr)) return(NULL);
+	if (/* error = */ FindSymbol(connID, funcName, &func, &symClass))
+		return(NULL);
+	
+	return(func);
+	}
+#endif /* GENERATINGPOWERPC */