new timer code (esp. HPET) now operational. TSC still disabled pending calibration code.

add update mechanism (needed to safely handle counter rollover) and
lock-free synchronization.

refactor:
. Counters now have Activate/Shutdown interface rather than throwing
exceptions from the ctor.
. whrt is responsible for caching frequency/resolution etc.
. fix counterBits handling

This was SVN commit r5105.
This commit is contained in:
janwas 2007-05-28 09:25:38 +00:00
parent bc519cc671
commit 52ae284a7e
14 changed files with 390 additions and 429 deletions

View File

@ -1,6 +1,6 @@
/**
* =========================================================================
* File : tick_source.h
* File : counter.h
* Project : 0 A.D.
* Description : Interface for timer implementations
* =========================================================================
@ -11,28 +11,34 @@
#ifndef INCLUDED_TICK_SOURCE
#define INCLUDED_TICK_SOURCE
class TickSourceUnavailable : public std::runtime_error
// derived implementations must be called CounterIMPL,
// where IMPL matches the WHRT_IMPL identifier. (see CREATE)
class ICounter : boost::noncopyable
{
public:
TickSourceUnavailable(const std::string& msg)
: std::runtime_error(msg)
{
}
};
class TickSource
{
public:
TickSource() {}
virtual ~TickSource() {}
virtual bool IsSafe() const = 0;
// (compiled-generated) ctor only sets up the vptr
virtual ~ICounter() {}
virtual const char* Name() const = 0;
virtual u64 Ticks() const = 0;
// Activate with an error return value is much cleaner+safer than
// throwing exceptions in the ctor.
virtual LibError Activate() = 0;
virtual void Shutdown() = 0;
virtual bool IsSafe() const = 0;
/**
* @return the current value of the counter (all but the lower
* CounterBits() bits must be zero)
**/
virtual u64 Counter() const = 0;
// note: implementations need not cache the following; that's taken
// care of by WHRT.
/**
* @return the bit width of the counter (<= 64)
* WHRT uses this to ensure the counter (running at nominal frequency)
* doesn't overflow more than once during CALIBRATION_INTERVAL_MS.
**/

View File

@ -28,7 +28,7 @@ struct HpetDescriptionTable
u8 attributes;
};
struct TickSourceHpet::HpetRegisters
struct CounterHPET::HpetRegisters
{
u64 capabilities;
u64 reserved1;
@ -51,77 +51,71 @@ static const u64 CONFIG_ENABLE = BIT64(0);
//-----------------------------------------------------------------------------
TickSourceHpet::TickSourceHpet()
LibError CounterHPET::Activate()
{
// (no need to check return value - valid hpet implies success)
(void)mahaf_Init();
(void)acpi_Init();
if(!mahaf_Init())
return ERR::FAIL; // NOWARN (no Administrator privileges)
if(!acpi_Init())
WARN_RETURN(ERR::FAIL);
const HpetDescriptionTable* hpet = (const HpetDescriptionTable*)acpi_GetTable("HPET");
if(!hpet)
throw TickSourceUnavailable("HPET: no ACPI table");
return ERR::NO_SYS; // NOWARN (HPET not reported by BIOS)
debug_assert(hpet->baseAddress.addressSpaceId == ACPI_AS_MEMORY);
m_hpetRegisters = (volatile HpetRegisters*)mahaf_MapPhysicalMemory(hpet->baseAddress.address, sizeof(HpetRegisters));
if(!m_hpetRegisters)
throw TickSourceUnavailable("HPET: map failed");
// get counter parameters
const u64 caps = m_hpetRegisters->capabilities;
const u32 timerPeriod_fs = bits64(caps, 32, 63);
m_frequency = 1e15 / timerPeriod_fs;
m_counterBits = (caps & CAP_SIZE64)? 64 : 32;
WARN_RETURN(ERR::NO_MEM);
// start the counter (if not already running)
// note: do not reset value to 0 to avoid interfering with any
// other users of the timer (e.g. Vista QPC)
m_hpetRegisters->config |= CONFIG_ENABLE;
return INFO::OK;
}
TickSourceHpet::~TickSourceHpet()
void CounterHPET::Shutdown()
{
mahaf_UnmapPhysicalMemory((void*)m_hpetRegisters);
mahaf_Shutdown();
acpi_Shutdown();
mahaf_Shutdown();
}
bool TickSourceHpet::IsSafe() const
bool CounterHPET::IsSafe() const
{
return false;
// the HPET being created to address other timers' problems, it has
// no issues of its own.
return true;
}
u64 TickSourceHpet::Ticks() const
u64 CounterHPET::Counter() const
{
u64 ticks = m_hpetRegisters->counterValue;
// note: the spec allows 32 or 64 bit counters. given the typical
// frequency of 14.318 MHz, worst case is rollover within 300 s. this is
// obviously not long enough to never happen more than once, so it must
// be handled; there is no benefit in using all 64 bits where available.
//
// note that limiting ourselves to 32 bits also avoids the potential
// headache of non-atomic bus reads.
ticks &= 0xFFFFFFFFu;
return ticks;
// note: we assume the data bus can do atomic 64-bit transfers,
// which has been the case since the original Pentium.
// (note: see implementation of GetTickCount for an algorithm to
// cope with non-atomic reads)
return m_hpetRegisters->counterValue;
}
/**
* WHRT uses this to ensure the counter (running at nominal frequency)
* doesn't overflow more than once during CALIBRATION_INTERVAL_MS.
**/
uint TickSourceHpet::CounterBits() const
uint CounterHPET::CounterBits() const
{
return 32;
const u64 caps = m_hpetRegisters->capabilities;
const uint counterBits = (caps & CAP_SIZE64)? 64 : 32;
return counterBits;
}
/**
* initial measurement of the tick rate. not necessarily correct
* (e.g. when using TSC: cpu_ClockFrequency isn't exact).
**/
double TickSourceHpet::NominalFrequency() const
double CounterHPET::NominalFrequency() const
{
return m_frequency;
const u64 caps = m_hpetRegisters->capabilities;
const u32 timerPeriod_fs = bits64(caps, 32, 63);
const double frequency = 1e15 / timerPeriod_fs;
return frequency;
}

View File

@ -11,23 +11,23 @@
#ifndef INCLUDED_HPET
#define INCLUDED_HPET
#include "tick_source.h"
#include "counter.h"
class TickSourceHpet : public TickSource
class CounterHPET : public ICounter
{
public:
TickSourceHpet();
virtual ~TickSourceHpet();
virtual const char* Name() const
{
return "HPET";
}
virtual LibError Activate();
virtual void Shutdown();
virtual bool IsSafe() const;
virtual u64 Ticks() const;
virtual u64 Counter() const;
/**
* WHRT uses this to ensure the counter (running at nominal frequency)
@ -42,10 +42,8 @@ public:
virtual double NominalFrequency() const;
private:
double m_frequency;
struct HpetRegisters;
volatile HpetRegisters* m_hpetRegisters;
uint m_counterBits;
};
#endif // #ifndef INCLUDED_HPET

View File

@ -18,7 +18,7 @@
// - reading it is slow and cannot be done by two independent users
// (the second being QPC) since the counter value must be latched.
//
// there are enough other tick sources anway.
// there are enough other counters anway.
static const i64 PIT_FREQ = 1193182; // (= master oscillator frequency/12)

View File

@ -29,28 +29,30 @@ struct FADT
static const u32 TMR_VAL_EXT = BIT(8);
TickSourcePmt::TickSourcePmt()
//-----------------------------------------------------------------------------
LibError CounterPMT::Activate()
{
// (no need to check return value - valid fadt implies success)
(void)mahaf_Init();
(void)acpi_Init();
if(!mahaf_Init())
return ERR::FAIL; // NOWARN (no Administrator privileges)
if(!acpi_Init())
WARN_RETURN(ERR::FAIL);
const FADT* fadt = (const FADT*)acpi_GetTable("FADT");
if(!fadt)
throw TickSourceUnavailable("PMT: no FADT");
WARN_RETURN(ERR::NO_SYS);
m_portAddress = u16_from_larger(fadt->pmTimerPortAddress);
m_counterBits = (fadt->flags & TMR_VAL_EXT)? 32 : 24;
return INFO::OK;
}
TickSourcePmt::~TickSourcePmt()
void CounterPMT::Shutdown()
{
acpi_Shutdown();
mahaf_Shutdown();
}
bool TickSourcePmt::IsSafe() const
bool CounterPMT::IsSafe() const
{
return false;
// the PMT has one issue: "Performance counter value may unexpectedly
// leap forward" (Q274323). This happens on some buggy Pentium-era
// systems under heavy PCI bus load. We are clever and observe that
@ -60,31 +62,28 @@ return false;
return true;
}
u64 TickSourcePmt::Ticks() const
u64 CounterPMT::Counter() const
{
u32 ticks = mahaf_ReadPort32(m_portAddress);
// note: the spec allows 24 or 32 bit counters. given the fixed
// frequency of 3.57 MHz, worst case is rollover within 4.6 s. this is
// obviously not long enough to never happen more than once, so it must
// be handled; there is no benefit in using all 32 bits where available.
ticks &= 0xFFFFFFu;
return (u64)ticks;
return mahaf_ReadPort32(m_portAddress);
}
/**
* WHRT uses this to ensure the counter (running at nominal frequency)
* doesn't overflow more than once during CALIBRATION_INTERVAL_MS.
**/
uint TickSourcePmt::CounterBits() const
uint CounterPMT::CounterBits() const
{
return m_counterBits;
const FADT* fadt = (const FADT*)acpi_GetTable("FADT");
debug_assert(fadt); // Activate made sure FADT is available
const uint counterBits = (fadt->flags & TMR_VAL_EXT)? 32 : 24;
return counterBits;
}
/**
* initial measurement of the tick rate. not necessarily correct
* (e.g. when using TSC: cpu_ClockFrequency isn't exact).
**/
double TickSourcePmt::NominalFrequency() const
double CounterPMT::NominalFrequency() const
{
return (double)PMT_FREQ;
}

View File

@ -11,24 +11,24 @@
#ifndef INCLUDED_PMT
#define INCLUDED_PMT
#include "tick_source.h"
#include "counter.h"
static const i64 PMT_FREQ = 3579545; // (= master oscillator frequency/4)
class TickSourcePmt : public TickSource
class CounterPMT : public ICounter
{
public:
TickSourcePmt();
virtual ~TickSourcePmt();
virtual const char* Name() const
{
return "PMT";
}
virtual LibError Activate();
virtual void Shutdown();
virtual bool IsSafe() const;
virtual u64 Ticks() const;
virtual u64 Counter() const;
/**
* WHRT uses this to ensure the counter (running at nominal frequency)
@ -44,7 +44,6 @@ public:
private:
u16 m_portAddress;
uint m_counterBits;
};
#endif // #ifndef INCLUDED_PMT

View File

@ -17,7 +17,7 @@
#include "pmt.h" // PMT_FREQ
TickSourceQpc::TickSourceQpc()
LibError CounterQPC::Activate()
{
// note: QPC is observed to be universally supported, but the API
// provides for failure, so play it safe.
@ -25,30 +25,32 @@ TickSourceQpc::TickSourceQpc()
LARGE_INTEGER qpcFreq, qpcValue;
const BOOL ok1 = QueryPerformanceFrequency(&qpcFreq);
const BOOL ok2 = QueryPerformanceCounter(&qpcValue);
if(!ok1 || !ok2 || !qpcFreq.QuadPart || !qpcValue.QuadPart)
throw TickSourceUnavailable("QPC not supported?!");
WARN_RETURN_IF_FALSE(ok1 && ok2);
if(!qpcFreq.QuadPart || !qpcValue.QuadPart)
WARN_RETURN(ERR::FAIL);
m_frequency = (i64)qpcFreq.QuadPart;
return INFO::OK;
}
TickSourceQpc::~TickSourceQpc()
void CounterQPC::Shutdown()
{
}
bool TickSourceQpc::IsSafe() const
bool CounterQPC::IsSafe() const
{
// the PIT is entirely safe (even if annoyingly slow to read)
if(m_frequency == PIT_FREQ)
return true;
// note: we have separate modules that directly access some of the
// tick sources potentially used by QPC. marking them or QPC unsafe is
// counters potentially used by QPC. marking them or QPC unsafe is
// risky because users can override either of those decisions.
// directly disabling them is ugly (increased coupling).
// instead, we'll make sure our implementations can coexist with QPC and
// verify the secondary reference timer has a different frequency.
// the PMT is safe (see discussion in TickSourcePmt::IsSafe);
// the PMT is safe (see discussion in CounterPmt::IsSafe);
if(m_frequency == PIT_FREQ)
return true;
@ -84,7 +86,7 @@ bool TickSourceQpc::IsSafe() const
return true;
}
u64 TickSourceQpc::Ticks() const
u64 CounterQPC::Counter() const
{
// fairly time-critical here, don't check the return value
// (IsSupported made sure it succeeded initially)
@ -97,25 +99,12 @@ u64 TickSourceQpc::Ticks() const
* WHRT uses this to ensure the counter (running at nominal frequency)
* doesn't overflow more than once during CALIBRATION_INTERVAL_MS.
**/
uint TickSourceQpc::CounterBits() const
uint CounterQPC::CounterBits() const
{
// note: the PMT is either 24 or 32 bits; older QPC implementations
// apparently had troubles with rollover.
// "System clock problem can inflate benchmark scores"
// (http://www.lionbridge.com/bi/cont2000/200012/perfcnt.asp ; no longer
// online, nor findable in Google Cache / archive.org) reports
// incorrect values every 4.6 seconds unless the timer is polled in
// the meantime. the given timeframe corresponds to 24 bits @ 3.57 MHz.
//
// we will therefore return the worst case value of 24 when using PMT
// (don't bother checking if it's 32-bit because there's no harm in
// ignoring the upper bits since we read it often enough)
if(m_frequency == PMT_FREQ)
return 24;
// no reports of trouble with the other implementations have surfaced,
// so we'll assume Windows correctly handles rollover and that we
// have the full 64 bits.
// there are reports of incorrect rollover handling in the PMT
// implementation of QPC (see CounterPMT::IsSafe). however, other
// counters would be used on those systems, so it's irrelevant.
// we'll report the full 64 bits.
return 64;
}
@ -123,7 +112,7 @@ uint TickSourceQpc::CounterBits() const
* initial measurement of the tick rate. not necessarily correct
* (e.g. when using TSC: cpu_ClockFrequency isn't exact).
**/
double TickSourceQpc::NominalFrequency() const
double CounterQPC::NominalFrequency() const
{
return (double)m_frequency;
}

View File

@ -11,22 +11,22 @@
#ifndef INCLUDED_QPC
#define INCLUDED_QPC
#include "tick_source.h"
#include "counter.h"
class TickSourceQpc : public TickSource
class CounterQPC : public ICounter
{
public:
TickSourceQpc();
virtual ~TickSourceQpc();
virtual const char* Name() const
{
return "QPC";
}
virtual LibError Activate();
virtual void Shutdown();
virtual bool IsSafe() const;
virtual u64 Ticks() const;
virtual u64 Counter() const;
/**
* WHRT uses this to ensure the counter (running at nominal frequency)
@ -41,7 +41,7 @@ public:
virtual double NominalFrequency() const;
private:
// cached because QPF is a bit slow.
// used in several places and QPF is a bit slow+cumbersome.
// (i64 allows easier conversion to double)
i64 m_frequency;
};

View File

@ -29,20 +29,22 @@
static const UINT PERIOD_MS = 2;
TickSourceTgt::TickSourceTgt()
LibError CounterTGT::Activate()
{
// note: timeGetTime is always available and cannot fail.
MMRESULT ret = timeBeginPeriod(PERIOD_MS);
debug_assert(ret == TIMERR_NOERROR);
return INFO::OK;
}
TickSourceTgt::~TickSourceTgt()
void CounterTGT::Shutdown()
{
timeEndPeriod(PERIOD_MS);
}
bool TickSourceTgt::IsSafe() const
bool CounterTGT::IsSafe() const
{
// the only point of criticism is the possibility of falling behind
// due to lost interrupts. this can happen to any interrupt-based timer
@ -51,7 +53,7 @@ bool TickSourceTgt::IsSafe() const
return true;
}
u64 TickSourceTgt::Ticks() const
u64 CounterTGT::Counter() const
{
return timeGetTime();
}
@ -60,7 +62,7 @@ u64 TickSourceTgt::Ticks() const
* WHRT uses this to ensure the counter (running at nominal frequency)
* doesn't overflow more than once during CALIBRATION_INTERVAL_MS.
**/
uint TickSourceTgt::CounterBits() const
uint CounterTGT::CounterBits() const
{
return 32;
}
@ -69,7 +71,7 @@ uint TickSourceTgt::CounterBits() const
* initial measurement of the tick rate. not necessarily correct
* (e.g. when using TSC: cpu_ClockFrequency isn't exact).
**/
double TickSourceTgt::NominalFrequency() const
double CounterTGT::NominalFrequency() const
{
return 1000.0;
}
@ -77,7 +79,7 @@ double TickSourceTgt::NominalFrequency() const
/**
* actual resolution [s]
**/
double TickSourceTgt::Resolution() const
double CounterTGT::Resolution() const
{
return PERIOD_MS*1e-3;
}

View File

@ -11,22 +11,22 @@
#ifndef INCLUDED_TGT
#define INCLUDED_TGT
#include "tick_source.h"
#include "counter.h"
class TickSourceTgt : public TickSource
class CounterTGT : public ICounter
{
public:
TickSourceTgt();
virtual ~TickSourceTgt();
virtual const char* Name() const
{
return "TGT";
}
virtual LibError Activate();
virtual void Shutdown();
virtual bool IsSafe() const;
virtual u64 Ticks() const;
virtual u64 Counter() const;
/**
* WHRT uses this to ensure the counter (running at nominal frequency)

View File

@ -29,13 +29,13 @@
struct PerCpuTscState
{
u64 calibrationTicks;
double calibrationTime;
u64 m_lastTicks;
double m_lastTime;
double m_observedFrequency;
// mark this struct used just in case cpu_CallByEachCPU doesn't ensure
// only one thread is running. a flag is safer than a magic APIC ID value.
uintptr_t isInitialized;
uint apicId;
float observedFrequency;
uintptr_t m_isInitialized;
uint m_apicId;
};
static const size_t MAX_CPUS = 32; // Win32 also imposes this limit
@ -46,7 +46,7 @@ static PerCpuTscState& NextUnusedPerCpuTscState()
for(size_t i = 0; i < MAX_CPUS; i++)
{
PerCpuTscState& cpuTscState = cpuTscStates[i];
if(cpu_CAS(&cpuTscState.isInitialized, 0, 1))
if(cpu_CAS(&cpuTscState.m_isInitialized, 0, 1))
return cpuTscState;
}
@ -59,7 +59,7 @@ static PerCpuTscState& CurrentCpuTscState()
for(size_t i = 0; i < MAX_CPUS; i++)
{
PerCpuTscState& cpuTscState = cpuTscStates[i];
if(cpuTscState.isInitialized && cpuTscState.apicId == apicId)
if(cpuTscState.m_isInitialized && cpuTscState.m_apicId == apicId)
return cpuTscState;
}
@ -71,10 +71,10 @@ static void InitPerCpuTscState(void* param) // callback
const double cpuClockFrequency = *(double*)param;
PerCpuTscState& cpuTscState = NextUnusedPerCpuTscState();
cpuTscState.apicId = ia32_ApicId();
cpuTscState.calibrationTicks = ia32_rdtsc();
cpuTscState.calibrationTime = 0.0;
cpuTscState.observedFrequency = cpuClockFrequency;
cpuTscState.m_apicId = ia32_ApicId();
cpuTscState.m_lastTicks = ia32_rdtsc();
cpuTscState.m_lastTime = 0.0;
cpuTscState.m_observedFrequency = cpuClockFrequency;
}
static LibError InitPerCpuTscStates(double cpuClockFrequency)
@ -84,6 +84,7 @@ static LibError InitPerCpuTscStates(double cpuClockFrequency)
return INFO::OK;
}
//-----------------------------------------------------------------------------
// note: calibration is necessary due to long-term thermal drift
@ -92,20 +93,20 @@ static LibError InitPerCpuTscStates(double cpuClockFrequency)
//-----------------------------------------------------------------------------
TickSourceTsc::TickSourceTsc()
LibError CounterTSC::Activate()
{
if(!ia32_cap(IA32_CAP_TSC))
throw TickSourceUnavailable("TSC: unsupported");
return ERR::NO_SYS; // NOWARN (CPU doesn't support RDTSC)
if(InitPerCpuTscStates(wcpu_ClockFrequency()) != INFO::OK)
throw TickSourceUnavailable("TSC: per-CPU init failed");
// RETURN_ERR(InitPerCpuTscStates(wcpu_ClockFrequency()));
return INFO::OK;
}
TickSourceTsc::~TickSourceTsc()
void CounterTSC::Shutdown()
{
}
bool TickSourceTsc::IsSafe() const
bool CounterTSC::IsSafe() const
{
return false;
@ -160,7 +161,7 @@ return false;
return false;
}
u64 TickSourceTsc::Ticks() const
u64 CounterTSC::Counter() const
{
return ia32_rdtsc();
}
@ -169,7 +170,7 @@ u64 TickSourceTsc::Ticks() const
* WHRT uses this to ensure the counter (running at nominal frequency)
* doesn't overflow more than once during CALIBRATION_INTERVAL_MS.
**/
uint TickSourceTsc::CounterBits() const
uint CounterTSC::CounterBits() const
{
return 64;
}
@ -178,7 +179,7 @@ uint TickSourceTsc::CounterBits() const
* initial measurement of the tick rate. not necessarily correct
* (e.g. when using TSC: cpu_ClockFrequency isn't exact).
**/
double TickSourceTsc::NominalFrequency() const
double CounterTSC::NominalFrequency() const
{
return wcpu_ClockFrequency();
}

View File

@ -11,22 +11,22 @@
#ifndef INCLUDED_TSC
#define INCLUDED_TSC
#include "tick_source.h"
#include "counter.h"
class TickSourceTsc : public TickSource
class CounterTSC : public ICounter
{
public:
TickSourceTsc();
~TickSourceTsc();
virtual const char* Name() const
{
return "TSC";
}
virtual LibError Activate();
virtual void Shutdown();
virtual bool IsSafe() const;
virtual u64 Ticks() const;
virtual u64 Counter() const;
/**
* WHRT uses this to ensure the counter (running at nominal frequency)

View File

@ -19,263 +19,289 @@
#include "lib/adts.h"
#include "lib/bits.h"
#include "tsc.h"
#include "hpet.h"
#include "pmt.h"
#include "qpc.h"
#include "tgt.h"
#include "tsc.h"
// to add a new counter type, simply include its header here and
// insert a case in ConstructCounterAt's switch statement.
#pragma SECTION_PRE_LIBC(L) // dependencies: wposix
#pragma SECTION_PRE_LIBC(D) // wposix depends on us
WIN_REGISTER_FUNC(whrt_Init);
#pragma FORCE_INCLUDE(whrt_Init)
#pragma SECTION_POST_ATEXIT(D)
#pragma SECTION_POST_ATEXIT(V)
WIN_REGISTER_FUNC(whrt_Shutdown);
#pragma FORCE_INCLUDE(whrt_Shutdown)
#pragma SECTION_RESTORE
// see http://www.gamedev.net/reference/programming/features/timing/ .
static bool IsTickSourceEstablished();
static int RolloversPerCalibrationInterval(double frequency, uint counterBits);
namespace ERR
{
const LibError WHRT_COUNTER_UNSAFE = 140000;
}
//-----------------------------------------------------------------------------
// safety recommendation / override
// create/destroy counters
// while we do our best to work around timer problems or avoid them if unsafe,
// future requirements and problems may be different. allow the user or app
// override TickSource::IsSafe decisions.
cassert(WHRT_DEFAULT == 0); // ensure 0 is the correct initializer
static WhrtOverride overrides[WHRT_NUM_TICK_SOURCES]; // indexed by WhrtTickSourceId
void whrt_OverrideRecommendation(WhrtTickSourceId id, WhrtOverride override)
/**
* @return pointer to a newly constructed ICounter subclass of type <id> at
* the given address, or 0 iff the ID is invalid.
* @param size receives the size [bytes] of the created instance.
**/
static ICounter* ConstructCounterAt(uint id, void* address, size_t& size)
{
// calling this function only makes sense when tick source hasn't
// been chosen yet
debug_assert(!IsTickSourceEstablished());
// rationale for placement new: see call site.
#define CREATE(impl)\
size = sizeof(Counter##impl);\
return new(address) Counter##impl();
debug_assert(id < WHRT_NUM_TICK_SOURCES);
overrides[id] = override;
}
#include "lib/nommgr.h" // MMGR interferes with placement new
static bool IsSafe(const TickSource* tickSource, WhrtTickSourceId id)
{
debug_assert(id < WHRT_NUM_TICK_SOURCES);
if(overrides[id] == WHRT_DISABLE)
return false;
if(overrides[id] == WHRT_FORCE)
return true;
return tickSource->IsSafe();
}
//-----------------------------------------------------------------------------
// manage tick sources
// use static array to avoid allocations (max #implementations is known)
static TickSource* tickSources[WHRT_NUM_TICK_SOURCES];
static uint nextTickSourceId = 0;
// factory
static TickSource* CreateTickSource(WhrtTickSourceId id)
{
// counters are chosen according to the following order. rationale:
// - TSC must come before QPC and PMT to make sure a bug in the latter on
// Pentium systems doesn't come up.
// - TGT really isn't as safe as the others, so it should be last.
// - low-overhead and high-resolution counters are preferred.
switch(id)
{
case WHRT_TSC:
return new TickSourceTsc();
case WHRT_QPC:
return new TickSourceQpc();
case WHRT_HPET:
return new TickSourceHpet();
case WHRT_PMT:
return new TickSourcePmt();
case WHRT_TGT:
return new TickSourceTgt();
NODEFAULT;
case 0:
CREATE(TSC)
case 1:
CREATE(HPET)
case 2:
CREATE(PMT)
case 3:
CREATE(QPC)
case 4:
CREATE(TGT)
default:
size = 0;
return 0;
}
#include "lib/mmgr.h"
#undef CREATE
}
/**
* @return the newly created and unique instance of the next tick source,
* or 0 if all have already been created.
*
* notes:
* - stores the tick source in tickSources[] with index = id.
* - don't always create all tick sources - some require 'lengthy' init.
* @return a newly created Counter of type <id> or 0 iff the ID is invalid.
**/
static TickSource* CreateNextBestTickSource()
static ICounter* CreateCounter(uint id)
{
// we placement-new the Counter classes in a static buffer.
// this is dangerous, but we are careful to ensure alignment. it is
// unusual and thus bad, but there's also one advantage: we avoid
// using global operator new before the CRT is initialized (risky).
//
// note: we can't just define these classes as static because their
// ctors (necessary for vptr initialization) will run during _cinit,
// which is already after our use of them.
static const size_t MEM_SIZE = 200; // checked below
static u8 mem[MEM_SIZE];
static u8* nextMem = mem;
u8* addr = (u8*)round_up((uintptr_t)nextMem, 16);
size_t size;
ICounter* counter = ConstructCounterAt(id, addr, size);
nextMem = addr+size;
debug_assert(nextMem < mem+MEM_SIZE); // had enough room?
return counter;
}
static inline void DestroyCounter(ICounter*& counter)
{
if(!counter)
return;
counter->Shutdown();
counter->~ICounter(); // must be called due to placement new
counter = 0;
}
//-----------------------------------------------------------------------------
// choose best available counter
// (moved into a separate function to simplify error handling)
static inline LibError ActivateCounter(ICounter* counter)
{
RETURN_ERR(counter->Activate());
if(!counter->IsSafe())
return ERR::WHRT_COUNTER_UNSAFE; // NOWARN (happens often)
return INFO::OK;
}
/**
* @return the newly created and unique instance of the next best counter
* that is deemed safe, or 0 if all have already been created.
**/
static ICounter* GetNextBestSafeCounter()
{
for(;;)
{
if(nextTickSourceId == WHRT_NUM_TICK_SOURCES)
return 0;
WhrtTickSourceId id = (WhrtTickSourceId)nextTickSourceId++;
static uint nextCounterId = 0;
ICounter* counter = CreateCounter(nextCounterId++);
if(!counter)
return 0; // tried all, none were safe
try
LibError err = ActivateCounter(counter);
if(err == INFO::OK)
{
TickSource* tickSource = CreateTickSource(id);
debug_printf("HRT/ create id=%d name=%s freq=%f\n", id, tickSource->Name(), tickSource->NominalFrequency());
debug_assert(tickSources[id] == 0);
tickSources[id] = tickSource;
return tickSource;
debug_printf("HRT/ using name=%s freq=%f\n", counter->Name(), counter->NominalFrequency());
return counter; // found a safe counter
}
catch(TickSourceUnavailable& e)
else
{
debug_printf("HRT/ create id=%d failed: %s\n", id, e.what());
char buf[100];
debug_printf("HRT/ activating %s failed: %s\n", counter->Name(), error_description_r(err, buf, ARRAY_SIZE(buf)));
DestroyCounter(counter);
}
}
}
static bool IsTickSourceAcceptable(TickSource* tickSource, WhrtTickSourceId id, TickSource* undesiredTickSource = 0)
{
// not (yet|successfully) created
if(!tickSource)
return false;
// it's the one we don't want (typically the primary source)
if(tickSource == undesiredTickSource)
return false;
// unsafe
if(!IsSafe(tickSource, id))
return false;
// duplicate source (i.e. frequency matches that of another)
for(uint id = 0; ; id++)
{
TickSource* tickSource2 = tickSources[id];
// not (yet|successfully) created
if(!tickSource2)
continue;
// if there are two sources with the same frequency, the one with
// higher precedence (lower ID) should be taken, so stop when we
// reach tickSource's ID.
if(tickSource == tickSource2)
break;
if(IsSimilarMagnitude(tickSource->NominalFrequency(), tickSource2->NominalFrequency()))
return false;
}
return true;
}
static TickSource* DetermineBestSafeTickSource(TickSource* undesiredTickSource = 0)
{
// until one is found or all have been created:
for(;;)
{
// check all existing sources in decreasing order of precedence
for(uint id = 0; id < WHRT_NUM_TICK_SOURCES; id++)
{
TickSource* tickSource = tickSources[id];
if(IsTickSourceAcceptable(tickSource, (WhrtTickSourceId)id, undesiredTickSource))
return tickSource;
}
// no acceptable source found; create the next one
if(!CreateNextBestTickSource())
return 0; // have already created all sources
}
}
static void ShutdownTickSources()
{
for(uint i = 0; i < WHRT_NUM_TICK_SOURCES; i++)
{
SAFE_DELETE(tickSources[i]);
}
}
//-----------------------------------------------------------------------------
// (primary) tick source
// counter that drives the timer
static TickSource* primaryTickSource;
static ICounter* counter;
static double nominalFrequency;
static double resolution;
static uint counterBits;
static u64 counterMask;
static bool IsTickSourceEstablished()
static void InitCounter()
{
return (primaryTickSource != 0);
};
static void ChooseTickSource()
{
// we used to support switching tick sources at runtime, but that's
// we used to support switching counters at runtime, but that's
// unnecessarily complex. it need and should only be done once.
debug_assert(!IsTickSourceEstablished());
debug_assert(counter == 0);
counter = GetNextBestSafeCounter();
debug_assert(counter != 0);
primaryTickSource = DetermineBestSafeTickSource();
nominalFrequency = counter->NominalFrequency();
resolution = counter->Resolution();
counterBits = counter->CounterBits();
const int rollovers = RolloversPerCalibrationInterval(primaryTickSource->NominalFrequency(), primaryTickSource->CounterBits());
debug_assert(rollovers <= 1);
counterMask = bit_mask64(counterBits);
// sanity checks
debug_assert(nominalFrequency >= 500.0);
debug_assert(resolution <= 2e-3);
debug_assert(8 <= counterBits && counterBits <= 64);
}
/// @return ticks (unspecified start point)
i64 whrt_Ticks()
static void ShutdownCounter()
{
const u64 ticks = primaryTickSource->Ticks();
return (i64)ticks;
DestroyCounter(counter);
}
double whrt_NominalFrequency()
static inline u64 Counter()
{
const double frequency = primaryTickSource->NominalFrequency();
return frequency;
return counter->Counter();
}
/// @return difference [ticks], taking rollover into account.
static inline u64 CounterDelta(u64 oldCounter, u64 newCounter)
{
return (newCounter - oldCounter) & counterMask;
}
double whrt_Resolution()
{
const double resolution = primaryTickSource->Resolution();
return resolution;
}
//-----------------------------------------------------------------------------
// timer state
/**
* stores all timer state shared between readers and the update thread.
* (must be POD because it's used before static ctors run.)
**/
struct TimerState
{
// current value of the counter.
u64 counter;
static u64 initialTicks;
// sum of all counter ticks since first update.
// rollover is not an issue (even at a high frequency of 10 GHz,
// it'd only happen after 58 years)
u64 ticks;
// total elapsed time [seconds] since first update.
// converted from tick deltas with the *then current* frequency
// (avoids retroactive changes when then frequency changes)
double time;
// current frequency that will be used to convert ticks to seconds.
double frequency;
};
// how do we detect when the old TimerState is no longer in use and can be
// freed? we use two static instances (avoids dynamic allocation headaches)
// and swap between them ('double-buffering'). it is assumed that all
// entered critical sections (the latching of TimerState fields) will have
// been exited before the next update comes around; if not, TimerState.time
// changes, the critical section notices and re-reads the new values.
static TimerState timerStates[2];
// note: exchanging pointers is easier than XORing an index.
static TimerState* volatile ts = &timerStates[0];
static TimerState* volatile ts2 = &timerStates[1];
static void UpdateTimerState()
{
// how can we synchronize readers and the update thread? locks are
// preferably avoided since they're dangerous and can be slow. what we
// need to ensure is that TimerState doesn't change while another thread is
// accessing it. the first step is to linearize the update, i.e. have it
// appear to happen in an instant (done by building a new TimerState and
// having it go live by switching pointers). all that remains is to make
// reads of the state variables consistent, done by latching them all and
// retrying if an update came in the middle of this.
const u64 counter = Counter();
const u64 deltaTicks = CounterDelta(ts->counter, counter);
ts2->counter = counter;
ts2->frequency = nominalFrequency;
ts2->ticks = ts->ticks + deltaTicks;
ts2->time = ts->time + deltaTicks/ts2->frequency;
ts = (TimerState*)InterlockedExchangePointer(&ts2, ts);
}
double whrt_Time()
{
i64 deltaTicks = whrt_Ticks() - initialTicks;
double seconds = deltaTicks / whrt_NominalFrequency();
return seconds;
retry:
// latch timer state (counter and time must be from the same update)
const double time = ts->time;
const u64 counter = ts->counter;
// ts changed after reading time. note: don't compare counter because
// it _might_ have the same value after two updates.
if(time != ts->time)
goto retry;
const u64 deltaTicks = CounterDelta(counter, Counter());
return (time + deltaTicks/ts->frequency);
}
// must be an object so we can CAS-in the pointer to it
#if 0
class Calibrator
{
// ticks at init or last calibration.
// ticks since then are scaled by 1/hrt_cur_freq and added to hrt_cal_time
// to yield the current time.
u64 lastTicks;
//IHighResTimer safe;
u64 safe_last;
double LastFreqs[8]; // ring buffer
// used to calibrate and second-guess the primary
static TickSource* secondaryTickSource;
// value of hrt_time() at last calibration. needed so that changes to
// hrt_cur_freq don't affect the previous ticks (example: 72 ticks elapsed,
// nominal freq = 8 => time = 9.0. if freq is calculated as 9, time would
// go backwards to 8.0).
static double hrt_cal_time = 0.0;
// current ticks per second; average of last few values measured in
// calibrate(). needed to prevent long-term drift, and because
// hrt_nominal_freq isn't necessarily correct. only affects the ticks since
@ -283,37 +309,17 @@ class Calibrator
double CurFreq;
};
calibrationTickSource = DetermineBestSafeTickSource(primaryTickSource);
// return seconds since init.
//
// split to allow calling from calibrate without recursive locking.
// (not a problem, but avoids a BoundsChecker warning)
static double time_lk()
{
debug_assert(hrt_cur_freq > 0.0);
debug_assert(hrt_cal_ticks > 0);
// elapsed ticks and time since last calibration
const i64 delta_ticks = ticks_lk() - hrt_cal_ticks;
const double delta_time = delta_ticks / hrt_cur_freq;
return hrt_cal_time + delta_time;
}
calibrationCounter = DetermineBestSafeCounter(counter);
IsSimilarMagnitude(counter->NominalFrequency(), counter2->NominalFrequency()
// measure current HRT freq - prevents long-term drift; also useful because
// hrt_nominal_freq isn't necessarily exact.
//
// lock must be held.
static void calibrate_lk()
{
debug_assert(hrt_cal_ticks > 0);
// we're called from a WinMM event or after thread wakeup,
// so the timer has just been updated.
// no need to determine tick / compensate.
// so the timer has just been updated. no need to determine tick / compensate.
// get elapsed HRT ticks
const i64 hrt_cur = ticks_lk();
@ -378,72 +384,73 @@ static void calibrate_lk()
//-----------------------------------------------------------------------------
// calibration thread
// update thread
// note: we used to discipline the HRT timestamp to the system time, so it
// was advantageous to wake up the calibration thread via WinMM event
// was advantageous to perform updates triggered by a WinMM event
// (reducing instances where we're called in the middle of a scheduler tick).
// since that's no longer relevant, we prefer using a thread, because that
// avoids the dependency on WinMM and its lengthy startup time.
// rationale: (+ and - are reasons for longer and shorter lengths)
// + minimize CPU usage
// + tolerate possibly low secondary tick source resolution
// + tolerate possibly low secondary counter resolution
// + ensure all threads currently using TimerState return from those
// functions before the next interval
// - notice frequency drift quickly enough
// - no more than 1 counter rollover per interval (this is checked via
// RolloversPerCalibrationInterval)
static const DWORD CALIBRATION_INTERVAL_MS = 1000;
static int RolloversPerCalibrationInterval(double frequency, uint counterBits)
{
const double period = BIT64(counterBits) / frequency;
const i64 period_ms = cpu_i64FromDouble(period*1000.0);
return CALIBRATION_INTERVAL_MS / period_ms;
}
// - ensure there's no more than 1 counter rollover per interval (this is
// checked via RolloversPerCalibrationInterval)
static const DWORD UPDATE_INTERVAL_MS = 1000;
static HANDLE hExitEvent;
static HANDLE hCalibrationThread;
static HANDLE hUpdateThread;
static unsigned __stdcall CalibrationThread(void* UNUSED(data))
static unsigned __stdcall UpdateThread(void* UNUSED(data))
{
debug_set_thread_name("whrt_calibrate");
debug_set_thread_name("whrt_UpdateThread");
for(;;)
{
const DWORD ret = WaitForSingleObject(hExitEvent, CALIBRATION_INTERVAL_MS);
const DWORD ret = WaitForSingleObject(hExitEvent, UPDATE_INTERVAL_MS);
// owner terminated or wait failed or exit event signaled - exit thread
if(ret != WAIT_TIMEOUT)
break;
/// Calibrate();
UpdateTimerState();
}
return 0;
}
static inline LibError InitCalibrationThread()
static inline LibError InitUpdateThread()
{
// make sure our interval isn't too long
// (counterBits can be 64 => BIT64 would overflow => calculate period/2
const double period_2 = BIT64(counterBits-1) / nominalFrequency;
const uint rolloversPerInterval = UPDATE_INTERVAL_MS / cpu_i64FromDouble(period_2*2.0*1000.0);
debug_assert(rolloversPerInterval <= 1);
hExitEvent = CreateEvent(0, TRUE, FALSE, 0); // manual reset, initially false
if(hExitEvent == INVALID_HANDLE_VALUE)
WARN_RETURN(ERR::LIMIT);
hCalibrationThread = (HANDLE)_beginthreadex(0, 0, CalibrationThread, 0, 0, 0);
if(!hCalibrationThread)
hUpdateThread = (HANDLE)_beginthreadex(0, 0, UpdateThread, 0, 0, 0);
if(!hUpdateThread)
WARN_RETURN(ERR::LIMIT);
return INFO::OK;
}
static inline void ShutdownCalibrationThread()
static inline void ShutdownUpdateThread()
{
// signal thread
BOOL ok = SetEvent(hExitEvent);
WARN_IF_FALSE(ok);
// the nice way is to wait for it to exit
if(WaitForSingleObject(hCalibrationThread, 100) != WAIT_OBJECT_0)
TerminateThread(hCalibrationThread, 0); // forcibly exit (dangerous)
if(WaitForSingleObject(hUpdateThread, 100) != WAIT_OBJECT_0)
TerminateThread(hUpdateThread, 0); // forcibly exit (dangerous)
CloseHandle(hExitEvent);
CloseHandle(hCalibrationThread);
CloseHandle(hUpdateThread);
}
@ -451,12 +458,11 @@ static inline void ShutdownCalibrationThread()
static LibError whrt_Init()
{
ChooseTickSource();
InitCounter();
// latch start times
initialTicks = whrt_Ticks();
UpdateTimerState(); // must come before InitUpdateThread to avoid race
// RETURN_ERR(InitCalibrationThread());
RETURN_ERR(InitUpdateThread());
return INFO::OK;
}
@ -464,8 +470,9 @@ static LibError whrt_Init()
static LibError whrt_Shutdown()
{
// ShutdownCalibrationThread();
ShutdownTickSources();
ShutdownUpdateThread();
ShutdownCounter();
return INFO::OK;
}

View File

@ -11,41 +11,7 @@
#ifndef INCLUDED_WHRT
#define INCLUDED_WHRT
// arranged in decreasing order of preference with values = 0..N-1 so that
// the next best timer can be chosen by incrementing a counter.
//
// rationale for the ordering:
// - TSC must come before QPC and PMT to make sure a bug in the latter on
// Pentium systems doesn't come up.
// - timeGetTime really isn't as safe as the others, so it should be last.
// - low-overhead and high-resolution tick sources are preferred.
enum WhrtTickSourceId
{
WHRT_TSC,
WHRT_HPET,
WHRT_PMT,
WHRT_QPC,
WHRT_TGT,
WHRT_NUM_TICK_SOURCES
};
enum WhrtOverride
{
// allow use of a tick source if available and we think it's safe.
WHRT_DEFAULT = 0, // (value obviates initialization of overrides[])
// override our IsSafe decision.
WHRT_DISABLE,
WHRT_FORCE
};
extern void whrt_OverrideRecommendation(WhrtTickSourceId id, WhrtOverride override);
extern i64 whrt_Ticks();
extern double whrt_NominalFrequency();
extern double whrt_Resolution();
extern double whrt_Time();
#endif // #ifndef INCLUDED_WHRT