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:
parent
bc519cc671
commit
52ae284a7e
@ -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.
|
||||
**/
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user