singrdk/base/Kernel/Singularity.Hal.LegacyPC/RtcPitState.cs

166 lines
5.6 KiB
C#

///////////////////////////////////////////////////////////////////////////////
//
// Microsoft Research Singularity
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// File: RtcPitState.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.Diagnostics;
using System.Threading;
namespace Microsoft.Singularity.Hal
{
// Shared time state between RTClock and Programmable Timer
[ CLSCompliant(false) ]
internal sealed class RtcPitState
{
/// <remarks>
/// System up time as measured by the <see>RTClock</see>. This value
/// is only updated by the RTClock.
/// </remarks>
internal long upTime = 0;
internal volatile int pitLastClock;
/// <remarks>
/// Last time returned by GetKernelTicks. Used to check for time
/// going backwards. This can occur in PIT value updates in the
/// scaling between PIT timebase and kernel time base.
/// </remarks>
internal long lastKernelTicks = 0;
internal RtcPitState()
{
this.pitLastClock = 0xffff;
this.upTime = 0;
}
[NoHeapAllocation]
internal static int ComputePitOffset(int pitPrev, int pitNow)
{
if (pitPrev >= pitNow) {
return pitPrev - pitNow;
}
return pitPrev + 0xffff - pitNow;
}
[NoHeapAllocation]
internal long GetKernelTicks(int pitNow)
{
int pitOffset = ComputePitOffset(this.pitLastClock, pitNow);
long delta = Timer8254.PitTicksToTimeSpanTicks(pitOffset);
long r = this.upTime + delta;
if (r < this.lastKernelTicks)
{
// This should only be by a few ticks.
// Something to look for if you are ever
// working on this code.
r = this.lastKernelTicks;
} else {
this.lastKernelTicks = r;
}
return r;
}
}
}