232 lines
8.2 KiB
C#
232 lines
8.2 KiB
C#
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Microsoft Research Singularity
|
||
|
//
|
||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||
|
//
|
||
|
// File: ClockLogger.cs
|
||
|
//
|
||
|
// Useful reference URLs:
|
||
|
// http://developer.intel.com/design/archives/periphrl/index.htm
|
||
|
// http://developer.intel.com/design/archives/periphrl/docs/7203.htm
|
||
|
// http://developer.intel.com/design/archives/periphrl/docs/23124406.htm
|
||
|
//
|
||
|
// The basic ideas for this driver come from the MMOSA code,
|
||
|
// though the implementation differs. This is partly because
|
||
|
// the code needs to run on Virtual PC and it isn't able to do
|
||
|
// a very accurate emulation of the i8254.
|
||
|
//
|
||
|
// There are two source available for timing - the Real-Time
|
||
|
// Clock (RTC) and the programmable interval timer (PIT). The
|
||
|
// standard PC RTC is based on derivatives of the Motorola
|
||
|
// MC146818A. It's able to provide the time with a resolution
|
||
|
// of 1 second and also has a programmable periodic interrupt.
|
||
|
//
|
||
|
// The programmable interrupt timer is based on the i8254. It
|
||
|
// can be programmed in a variety of modes - we use it to
|
||
|
// generate an interrupt at a configurable time in the future
|
||
|
// and then reprogram it each interrupt. The maximum interrupt
|
||
|
// period is 65535 ticks of a 1.193MHz clock.
|
||
|
//
|
||
|
// We use both of the RTC and the programmable interrupt timer to
|
||
|
// maintain our estimate of the current time. The RTC provides granularity
|
||
|
// to with 1/64 seconds and the time is used to get an estimate to within
|
||
|
// 1/1.193 * 10e-6 seconds within each RTC interval.
|
||
|
//
|
||
|
// The key variables are:
|
||
|
//
|
||
|
// upTime - the time the system has been up. This variable gets
|
||
|
// updated during the periodic RTC interrupt handling
|
||
|
// (delta = 1/64Hz).
|
||
|
//
|
||
|
// pitLast - the last value programmed into the PIT. The PIT counts down
|
||
|
// and generates an interrupt at (or shortly after) the instant
|
||
|
// the current PIT value reaches zero.
|
||
|
//
|
||
|
// pitAccum - the accumulated time measured by the PIT since upTime
|
||
|
// was updated.
|
||
|
//
|
||
|
// The current kernel time is always:
|
||
|
// upTime + pitAccum + (pitLast - pitCurrent)
|
||
|
//
|
||
|
// The PIT is always programmed to run, either by the consumer of the timer
|
||
|
// interface or by the timer itself.
|
||
|
//
|
||
|
// Timer::SetNextInterrupt(t)
|
||
|
// pitAccum += (pitLast - pitCurrent)
|
||
|
// // program PIT (not shown)
|
||
|
// pitLast = t
|
||
|
//
|
||
|
// Timer::Interrupt()
|
||
|
// pitAccum += pitLast;
|
||
|
// // But PIT time may accumulate between interrupt dispatch and crossing
|
||
|
// // Zero so.
|
||
|
// if (pitCurrent != 0)
|
||
|
// pitAccum += (MaxPitValue - pitCurrent)
|
||
|
// // Inform user of interrupt
|
||
|
// if (userNotScheduledInterrupt)
|
||
|
// SetNextInterrupt(MaxInterruptInterval)
|
||
|
//
|
||
|
// RTC::Interrupt()
|
||
|
// pitLast = pitNow
|
||
|
// pitAccum = 0
|
||
|
// upTime += RTCInterruptPeriod
|
||
|
//
|
||
|
// All of these methods are atomic interrupt-wise.
|
||
|
//
|
||
|
// Note: if we want to test the accuracy of the timer over a
|
||
|
// period we can set RTC::Interrupt to just return without
|
||
|
// touching any variables. All of the time accumulated will end
|
||
|
// up in pitAccum.
|
||
|
//
|
||
|
// Conditionals
|
||
|
//
|
||
|
// TIMER_NO_GO - disables timer and scheduling of timer interrupts.
|
||
|
//
|
||
|
// RTC_NO_GO - disable RTC.
|
||
|
//
|
||
|
// DEBUG_TIMER - enable timer debug messages and boot-time diagnostics.
|
||
|
//
|
||
|
// DEBUG_CLOCK - enable clock debug messages
|
||
|
//
|
||
|
// LOG_CLOCK - log adjustments to clock time and dump out later.
|
||
|
//
|
||
|
// LOG_SNI - log calls to SetNextInterrupt to see what's being thrown in.
|
||
|
//
|
||
|
// Tip: When this code does not behave useful things to check
|
||
|
// are the interrupt rate and the rate of calls to
|
||
|
// SetNextInterrupt.
|
||
|
//
|
||
|
|
||
|
// #define VERBOSE
|
||
|
|
||
|
using Microsoft.Singularity.Io;
|
||
|
|
||
|
using System;
|
||
|
using System.Runtime.CompilerServices;
|
||
|
using System.Threading;
|
||
|
using System.Diagnostics;
|
||
|
|
||
|
namespace Microsoft.Singularity.Hal
|
||
|
{
|
||
|
[ CLSCompliant(false) ]
|
||
|
internal sealed class ClockLogger
|
||
|
{
|
||
|
public struct Record
|
||
|
{
|
||
|
public int who;
|
||
|
public ulong when;
|
||
|
public ulong currentTime;
|
||
|
public long upTime;
|
||
|
public int pitLastClock;
|
||
|
public int pitNow;
|
||
|
public long cookie;
|
||
|
};
|
||
|
private static int currentRecord = 0;
|
||
|
private static Record [] entries = null;
|
||
|
private static int ignoreCount = 20000;
|
||
|
|
||
|
static ClockLogger()
|
||
|
{
|
||
|
InitializeEntries();
|
||
|
#if LOG_CLOCK
|
||
|
DebugStub.Print("Initializing ClockLogger.\n");
|
||
|
#endif // LOG_CLOCK
|
||
|
}
|
||
|
|
||
|
[System.Diagnostics.Conditional("LOG_CLOCK")]
|
||
|
internal static void InitializeEntries()
|
||
|
{
|
||
|
entries = new Record[10000];
|
||
|
}
|
||
|
|
||
|
[System.Diagnostics.Conditional("LOG_CLOCK")]
|
||
|
[NoHeapAllocation]
|
||
|
internal static void AddEntry(int who, RtcPitState rps, TimerOmap3430 timer, long cookie)
|
||
|
{
|
||
|
if (ignoreCount != 0) {
|
||
|
ignoreCount--;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (currentRecord == entries.Length)
|
||
|
return;
|
||
|
|
||
|
entries[currentRecord].who = who;
|
||
|
entries[currentRecord].cookie = cookie;
|
||
|
entries[currentRecord].when = Processor.CycleCount;
|
||
|
// int pitNow = timer.Timer2Read();
|
||
|
int pitNow = 0;
|
||
|
entries[currentRecord].currentTime =
|
||
|
(ulong)rps.GetKernelTicks(pitNow);
|
||
|
entries[currentRecord].upTime = rps.upTime;
|
||
|
entries[currentRecord].pitLastClock = rps.pitLastClock;
|
||
|
entries[currentRecord].pitNow = pitNow;
|
||
|
|
||
|
currentRecord = currentRecord + 1;
|
||
|
|
||
|
if (currentRecord == entries.Length) {
|
||
|
bool iflag = Processor.DisableInterrupts();
|
||
|
try {
|
||
|
DumpEntries();
|
||
|
DebugStub.Assert(false);
|
||
|
}
|
||
|
finally {
|
||
|
Processor.RestoreInterrupts(iflag);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
[System.Diagnostics.Conditional("LOG_CLOCK")]
|
||
|
[NoHeapAllocation]
|
||
|
internal static void AddEntry(int who, RtcPitState rps, TimerOmap3430 timer)
|
||
|
{
|
||
|
AddEntry(who, rps, timer, 0);
|
||
|
}
|
||
|
|
||
|
[System.Diagnostics.Conditional("LOG_CLOCK")]
|
||
|
[NoHeapAllocation]
|
||
|
internal static void SetCookie(long cookie)
|
||
|
{
|
||
|
if (currentRecord < entries.Length)
|
||
|
entries[currentRecord].cookie = cookie;
|
||
|
}
|
||
|
|
||
|
[NoHeapAllocation]
|
||
|
static ulong ToMicros(ulong t, ulong t0, ulong tps)
|
||
|
{
|
||
|
return ((t - t0) * 10000000) / tps;
|
||
|
}
|
||
|
|
||
|
[System.Diagnostics.Conditional("LOG_CLOCK")]
|
||
|
[NoHeapAllocation]
|
||
|
static void DumpEntries()
|
||
|
{
|
||
|
DebugStub.Print("# Clock column 1 who\n");
|
||
|
DebugStub.Print("# Clock column 2 time in cpu cycles\n");
|
||
|
DebugStub.Print("# Clock column 3 time from clock\n");
|
||
|
DebugStub.Print("# Clock column 4 pitLastClock\n");
|
||
|
DebugStub.Print("# Clock column 5 pitNow\n");
|
||
|
DebugStub.Print("# Clock column 6 upTime\n");
|
||
|
DebugStub.Print("# Clock column 7 currentTime\n");
|
||
|
DebugStub.Print("# Clock column 8 cookie\n");
|
||
|
DebugStub.Print("# Clock column 9 record number\n");
|
||
|
|
||
|
for (int i = 0; i != currentRecord; i++) {
|
||
|
DebugStub.Print("Clock {0:d6} {1:d6} {2} {3} {4} {5} {6} {7}\n",
|
||
|
__arglist(
|
||
|
ToMicros(entries[i].when,
|
||
|
entries[0].when,
|
||
|
Processor.CyclesPerSecond),
|
||
|
ToMicros(entries[i].currentTime, 0, 10000000),
|
||
|
entries[i].pitLastClock,
|
||
|
entries[i].pitNow,
|
||
|
entries[i].upTime,
|
||
|
entries[i].currentTime,
|
||
|
entries[i].cookie,
|
||
|
i));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|