283 lines
10 KiB
C#
283 lines
10 KiB
C#
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Microsoft Research Singularity
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//
|
|
// File: CalibrateTimers.cs
|
|
//
|
|
// Note:
|
|
//
|
|
|
|
// #define VERBOSE
|
|
|
|
using System;
|
|
using Microsoft.Singularity.Hal;
|
|
using Microsoft.Singularity.Hal.Acpi;
|
|
|
|
namespace Microsoft.Singularity.Hal
|
|
{
|
|
[ CLSCompliant(false) ]
|
|
internal sealed class CalibrateTimers
|
|
{
|
|
private static int ApproxLog10(ulong value)
|
|
{
|
|
ulong thresh = 4;
|
|
for (int i = 0; i < 20; i++)
|
|
{
|
|
if (thresh >= value)
|
|
{
|
|
return i;
|
|
}
|
|
thresh *= 10;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
private static int MinSpread(ulong [] values, int width)
|
|
{
|
|
ulong maxIre = 0; // Inverse-relative error
|
|
int minCenter = 0;
|
|
|
|
for (int i = 0; i < values.Length - width; i++)
|
|
{
|
|
ulong delta = values[i + width - 1] - values[i];
|
|
ulong ire = values[i + width / 2] / (delta + 1);
|
|
if (ire > maxIre)
|
|
{
|
|
maxIre = ire;
|
|
minCenter = i + width / 2;
|
|
}
|
|
}
|
|
return minCenter;
|
|
}
|
|
|
|
private static void DisplayResults(ulong tscHz, int i8254Hz)
|
|
{
|
|
DebugStub.Print("TSC measured at {0} MHz\n",
|
|
__arglist((int)(tscHz / 1000000)));
|
|
DebugStub.Print("i8254 measured at {0} Hz\n",
|
|
__arglist(i8254Hz));
|
|
}
|
|
|
|
internal static bool Run(PMTimer pmtimer, Timer8254 i8254)
|
|
{
|
|
const int testRuns = 8;
|
|
int attempts = 0;
|
|
|
|
const uint pmMask = 0xffffffu;
|
|
const ulong tscMask = 0xffffffffffff;
|
|
const int i8254Mask = 0xffff;
|
|
|
|
ulong [] tscHz = new ulong[testRuns];
|
|
ulong [] i8254Hz = new ulong[testRuns];
|
|
uint [] pmGoal = new uint[testRuns];
|
|
uint [] pmActual = new uint[testRuns];
|
|
|
|
i8254.Timer2Start();
|
|
|
|
measure:
|
|
attempts ++;
|
|
for (uint i = 0; i < testRuns; i++)
|
|
{
|
|
uint testHz = 2u + (i % 5u);
|
|
|
|
uint pmAccum = 0;
|
|
uint pmLast = pmtimer.Value & pmMask;
|
|
uint pmEnd = PMTimer.FrequencyHz / testHz;
|
|
|
|
ulong tscStart = Processor.CycleCount;
|
|
ulong tscEnd = 0;
|
|
|
|
int i8254Accum = 0;
|
|
int i8254Last = i8254.Timer2Read();
|
|
|
|
while (pmAccum < pmEnd)
|
|
{
|
|
// Spin to burn on few
|
|
// clock cycles. On real hardware we stall
|
|
// reading the timers. On VPC we see the
|
|
// PMTimer go backwards occasionally when
|
|
// hammering it so we call SpinWait to
|
|
// reduce the chance of observing it go
|
|
// backwards.
|
|
//
|
|
// Note: We explicitly use Thread.SpinWait
|
|
// since it will not be optimized out,
|
|
// unlike a simple spin loop.
|
|
System.Threading.Thread.SpinWait(10000);
|
|
|
|
uint pmNow = pmtimer.Value & pmMask;
|
|
if (pmNow < pmLast) {
|
|
// On VPC the PM
|
|
// timer occasionally goes backwards and
|
|
// this leads to bogus calibration
|
|
// points.
|
|
if (pmLast - pmNow < PMTimer.FrequencyHz / 2) {
|
|
// ignore measurement
|
|
continue;
|
|
}
|
|
// Clock wrap, measurement okay.
|
|
}
|
|
pmAccum += (pmNow + (pmMask + 1) - pmLast) & pmMask;
|
|
pmLast = pmNow;
|
|
|
|
tscEnd = Processor.CycleCount;
|
|
|
|
int i8254Now = i8254.Timer2Read();
|
|
i8254Accum += ((i8254Mask + 1) + i8254Last - i8254Now) &
|
|
i8254Mask;
|
|
i8254Last = i8254Now;
|
|
}
|
|
|
|
ulong tscDelta = (tscEnd + (tscMask + 1) - tscStart) & tscMask;
|
|
|
|
tscHz[i] = tscDelta * PMTimer.FrequencyHz / pmAccum;
|
|
i8254Hz[i] = (ulong)i8254Accum * PMTimer.FrequencyHz / pmAccum;
|
|
pmGoal[i] = pmEnd;
|
|
pmActual[i] = pmAccum;
|
|
}
|
|
|
|
#region -*- temporary failure debugging support -*-
|
|
|
|
ulong[] tscHzBackup = new ulong[tscHz.Length];
|
|
ulong[] i8254HzBackup = new ulong[i8254Hz.Length];
|
|
Array.Copy(tscHz, tscHzBackup, tscHz.Length);
|
|
Array.Copy(i8254Hz, i8254HzBackup, i8254Hz.Length);
|
|
#endregion
|
|
|
|
Array.Sort(tscHz);
|
|
Array.Sort(i8254Hz);
|
|
|
|
bool noisyClock = false;
|
|
|
|
// Re-measure if values are obviously bogus as can occur with
|
|
// VPC. Check is minimum value is less than n% of maximum
|
|
// value.
|
|
bool tscFailed = tscHz[0] < 3 * tscHz[testRuns - 1] / 4;
|
|
bool i8254Failed = i8254Hz[0] < 3 * i8254Hz[testRuns - 1] / 4;
|
|
|
|
if (tscFailed || i8254Failed) {
|
|
if (attempts < 10) {
|
|
noisyClock = true;
|
|
DebugStub.Print("CLOCK CALIBRATION FAILED");
|
|
if (tscFailed && i8254Failed) {
|
|
DebugStub.Print(" (tsc and i2854) ");
|
|
}
|
|
else if (tscFailed) {
|
|
DebugStub.Print(" (tsc only) " );
|
|
}
|
|
else {
|
|
DebugStub.Print(" (i8254 only) " );
|
|
}
|
|
DebugStub.Print("RETRYING.\n");
|
|
for (int i = 0; i < tscHzBackup.Length; i++) {
|
|
DebugStub.Print("tscHz {0} i8254Hz {1} pmGoal {2} pmActual {3}\n",
|
|
__arglist(tscHzBackup[i],
|
|
i8254HzBackup[i],
|
|
pmGoal[i],
|
|
pmActual[i]));
|
|
}
|
|
goto measure;
|
|
}
|
|
else {
|
|
DebugStub.Print("ALL CLOCK CALIBRATION ATTEMPTS FAILED.\n");
|
|
//
|
|
// TODO:
|
|
// Try to calibrate with rtClock
|
|
// SlowCalibration(rtClock, timer8254);
|
|
Processor.CyclesPerSecond = 1800 * 1000 * 1000;
|
|
i8254.SetTicksPerSecond(1193180);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
ulong tscHzEstimate = tscHz[MinSpread(tscHz, 3)];
|
|
int i8254HzEstimate = (int) i8254Hz[MinSpread(i8254Hz, 3)];
|
|
|
|
DisplayResults(tscHzEstimate, i8254HzEstimate);
|
|
|
|
// Set measured frequencies in appropriate places.
|
|
Processor.CyclesPerSecond = tscHzEstimate;
|
|
i8254.SetTicksPerSecond(i8254HzEstimate);
|
|
|
|
//
|
|
// Range of measurements is a pretty good indicator of VPCness
|
|
// since it may have been descheduled during these measurements
|
|
// so timer measurements will be poor.
|
|
//
|
|
int oValue = ApproxLog10(tscHzEstimate);
|
|
int oRange = ApproxLog10(tscHz[testRuns - 1] - tscHz[0]);
|
|
if (oValue - oRange < 5)
|
|
{
|
|
DebugStub.Print("*** Noisy timing measurements. Looks like measurement on VPC or bad hardware. ***\n");
|
|
noisyClock = true;
|
|
}
|
|
return noisyClock;
|
|
}
|
|
|
|
internal static void Run(RTClock rtc, Timer8254 i8254)
|
|
{
|
|
// This test uses the RTC's update-in-progress bit, UIP, as
|
|
// a measure of 1 second of time. The UIP set period is
|
|
// around 240us which means we can safely read the
|
|
// i8254 in a loop without worrying about missing the
|
|
// bit being set / cleared because of i/o operations.
|
|
//
|
|
// This test fails horribly on VPC. It has problems with the
|
|
// update-in-progress bit in the RTC. Fortunately we should
|
|
// not get here on VPC.
|
|
//
|
|
// NB This routine does not try to be as rigorous as
|
|
// the PM Timer version as each calibration run
|
|
// takes much longer.
|
|
|
|
const int testRuns = 2;
|
|
|
|
int i8254Last = 0;
|
|
int i8254Now = 0;
|
|
int i8254Accum = 0;
|
|
int [] i8254Hz = new int [testRuns];
|
|
|
|
ulong tscLast = 0;
|
|
ulong tscNow = 0;
|
|
ulong [] tscHz = new ulong[testRuns];
|
|
|
|
i8254.Timer2Start();
|
|
|
|
do
|
|
{
|
|
tscLast = Processor.CycleCount;
|
|
i8254Last = i8254.Timer2Read();
|
|
} while (rtc.UpdateInProgress() == false);
|
|
|
|
for (int i = 0; i < testRuns; i++)
|
|
{
|
|
while (rtc.UpdateInProgress() == true)
|
|
;
|
|
|
|
do
|
|
{
|
|
tscNow = Processor.CycleCount;
|
|
|
|
i8254Now = i8254.Timer2Read();
|
|
i8254Accum += (0x10000 + i8254Last - i8254Now) & 0xffff;
|
|
i8254Last = i8254Now;
|
|
}
|
|
while (rtc.UpdateInProgress() == false);
|
|
|
|
tscHz[i] = (tscNow + 0x1000000000000 - tscLast)&0xffffffffffff;
|
|
tscLast = tscNow;
|
|
|
|
i8254Hz[i] = i8254Accum;
|
|
i8254Accum = 0;
|
|
}
|
|
|
|
DisplayResults(tscHz[testRuns - 1], i8254Hz[testRuns - 1]);
|
|
|
|
Processor.CyclesPerSecond = tscHz[testRuns - 1];
|
|
i8254.SetTicksPerSecond(i8254Hz[testRuns - 1]);
|
|
}
|
|
}
|
|
}
|