/////////////////////////////////////////////////////////////////////////////// // // Microsoft Research Singularity // // Copyright (c) Microsoft Corporation. All rights reserved. // // File: RTClock.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.Threading; using System.Diagnostics; using System.Runtime.CompilerServices; using Microsoft.Singularity.Configuration; namespace Microsoft.Singularity.Hal { // declare resources for the kernel manifest [DriverCategory] [Signature("/pnp/PNP0B00")] public sealed class RtcResourcesLegacyPC : DriverCategoryDeclaration { [IoPortRange(0, Default = 0x70, Length = 0x02)] public IoPortRange port1; [IoIrqRange(1, Default = 0x08)] public IoIrqRange irq1; } /// /// Class RTClock represents the system /// Real-Time Clock. RTC chips in PCs are based on the /// Motorola MC1461818A. It provides timing resolution of 1 /// second, but can generate periodic interrupts more /// frequently. By combining information from /// Timer8254 programmable timer time can be read /// with a resolution of 0.838 microseconds. /// [ CLSCompliant(false) ] internal sealed class RTClockLegacyPC : HalClock { private PnpConfig config; private byte irq; private Pic pic; // Real time clock registers - for calibration private IoPort rtcadd; private IoPort rtcdat; // Data registers private const byte DS1287_SECONDS = 0x00; private const byte DS1287_SECONDS_ALARM = 0x01; private const byte DS1287_MINUTES = 0x02; private const byte DS1287_MINUTES_ALARM = 0x03; private const byte DS1287_HOURS = 0x04; private const byte DS1287_HOURS_ALARM = 0x05; private const byte DS1287_DAY_OF_WEEK = 0x06; // 1 is Sunday private const byte DS1287_DAY_OF_MONTH = 0x07; private const byte DS1287_MONTH = 0x08; private const byte DS1287_YEAR = 0x09; private const byte DS1287_USERDATA = 0x32; // Randomly chosen user reg. // Control registers and masks private const byte DS1287_A = 0x0a; private const byte DS1287_A_UIP = 0x80; // Update Cycle Status private const byte DS1287_A_DIVON = 0x20; // Osc and Freq Div on private const byte DS1287_A_8KHZ = 0x03; private const byte DS1287_A_4KHZ = 0x04; private const byte DS1287_A_2KHZ = 0x05; private const byte DS1287_A_1KHZ = 0x06; private const byte DS1287_A_64HZ = 0x0a; private const byte DS1287_A_2HZ = 0x0f; private const byte DS1287_B = 0x0b; private const byte DS1287_B_UTI = 0x80; // Update Transfer Inhibit private const byte DS1287_B_PIE = 0x40; // Periodic Interrupt Enable private const byte DS1287_B_24H = 0x20; // Alarm Interrupt Enable private const byte DS1287_B_UIE = 0x10; // Update Cycle Inter. Enable private const byte DS1287_B_SQWE = 0x08; // Square-Wave Enable private const byte DS1287_B_DF = 0x04; // Data Format private const byte DS1287_B_DF_BINARY = 0x04; // Binary private const byte DS1287_B_DF_BCD = 0x00; // Binary Coded Decimal (BCD) private const byte DS1287_B_HF = 0x02; // Hour Format private const byte DS1287_B_HF_24H = 0x02; // 24-hour format private const byte DS1287_B_HF_12H = 0x00; // 12-hour format private const byte DS1287_B_DSE = 0x01; // Daylight Saving Enable private const byte DS1287_C = 0x0c; private const byte DS1287_C_UF = 0x10; // Update Event Flag private const byte DS1287_C_AF = 0x20; // Alarm Event Flag private const byte DS1287_C_PF = 0x40; // Periodic Event Flag private const byte DS1287_C_INTF = 0x80; // Interrupt Request Flag private const byte DS1287_D = 0x0d; private const byte DS1287_D_VRT = 0x80; // Valid RAM and Time // Rate if rtc used for generating profiling interrupts const byte DS1287_A_SAMPLING_HZ = DS1287_A_1KHZ; const int InterruptGapTicks = 156250; // units of 100ns const uint maxAttempts = 1000000; private RtcPitState rps = null; private Timer8254LegacyPC timer = null; private long rtcBootTime; private volatile uint irqCount = 0; // Variables for fast GetKernelTicks implementation that uses // TSC when it is deemed safe. bool noisyTimer; // Running on VPC? Disable optimization long tscSnapshot; // TSC value at sync point long ticksSnapshot; // Kernel ticks at sync point long lastKernelTicks; // Last kernel ticks reported bool tscSnapshotValid = false; bool initedFastTime = false; int tickScale; const int tickRoll = 24; // Constructor internal RTClockLegacyPC(PnpConfig config, Pic pic, Timer8254LegacyPC timer) { DebugStub.Print("RTClock: create\n"); // /pnp/08/03/01/PNP0B00 0003 cfg dis : ISA RTC Controller : AT RTC // IRQ mask=0100 type=47 // I/O Port inf=01 min=0070 max=0070 aln=01 len=02 0070..0071 this.config = config; this.pic = pic; this.irq = ((IoIrqRange)config.DynamicRanges[1]).Irq; this.timer = timer; rtcadd = ((IoPortRange)config.DynamicRanges[0]) .PortAtOffset(0, 1, Access.ReadWrite); rtcdat = ((IoPortRange)config.DynamicRanges[0]) .PortAtOffset(1, 1, Access.ReadWrite); } [NoHeapAllocation] private byte ReadRtc(byte addr) { IoResult result; byte value; result = rtcadd.Write8NoThrow(addr); DebugStub.Assert(IoResult.Success == result); result = rtcdat.Read8NoThrow(out value); DebugStub.Assert(IoResult.Success == result); return value; } [NoHeapAllocation] private void WriteRtc(byte addr, byte val) { IoResult result; result = rtcadd.Write8NoThrow(addr); DebugStub.Assert(IoResult.Success == result); result = rtcdat.Write8NoThrow(val); DebugStub.Assert(IoResult.Success == result); } [NoHeapAllocation] public override void ClearInterrupt() { #if RTC_NO_GO DebugStub.Print("Unwanted RTC interrupt! (b = {0})\n", __arglist(ReadRtc(DS1287_B))); return; #endif // RTC_NO_GO byte status = ReadRtc(DS1287_C); if ((status & 0x40) != 0x40) { // We may get Update-Ended interrupts even though we've // not requested them. pic.AckIrq(irq); return; } ClockLogger.AddEntry(4, rps, timer); rps.pitLastClock = timer.Timer2Read(); rps.upTime += InterruptGapTicks; ClockLogger.AddEntry(5, rps, timer); irqCount++; if (timer.InterruptPending == false) { // This is to keep time progressing if the user has // not set an interrupt timer.SetKeepAliveInterrupt(); } pic.AckIrq(irq); // Invalidate TSC snapshot to force clock synchronization this.tscSnapshotValid = false; } internal byte Initialize() { DebugStub.Print("RTClock: initialize\n"); rps = timer.rps; // Turn oscillator on, but disable and clear interrupts if (Processor.SamplingEnabled()) { WriteRtc(DS1287_A, DS1287_A_DIVON | DS1287_A_SAMPLING_HZ); } else { WriteRtc(DS1287_A, DS1287_A_DIVON | DS1287_A_64HZ); } WriteRtc(DS1287_B, DS1287_B_24H | DS1287_B_DF_BCD); ReadRtc(DS1287_C); // Clear any update bits if ((ReadRtc(DS1287_D) & DS1287_D_VRT) == 0) { DebugStub.Print("RTClock weak or defective power source.\n"); } rtcBootTime = PullRtcTime().Ticks; // Enable and clear interrupts // NB it may take 500ms for first interrupt to be generated. pic.EnableIrq(irq); return pic.IrqToInterrupt(irq); } internal void Start() { DebugStub.Print("RTClock::Start()\n"); #if RTC_NO_GO WriteRtc(DS1287_B, DS1287_B_24H | DS1287_B_UTI); #endif // XXX A #else does not work here with sgc (!) #if !RTC_NO_GO if (Processor.SamplingEnabled()) { WriteRtc(DS1287_A, DS1287_A_DIVON | DS1287_A_SAMPLING_HZ); } else { WriteRtc(DS1287_A, DS1287_A_DIVON | DS1287_A_64HZ); } WriteRtc(DS1287_B, DS1287_B_24H | DS1287_B_PIE); ReadRtc(DS1287_C); #endif } internal void ReleaseResources() { pic.DisableIrq(irq); } [NoHeapAllocation] bool InitializeFastTime() { const ulong KernelTicksPerSecond = 10000000; ulong cps = Processor.CyclesPerSecond; if (cps >= KernelTicksPerSecond) { this.tscSnapshot = 0; this.ticksSnapshot = 0; this.tickScale = (int)((1 << tickRoll) * KernelTicksPerSecond / cps); #if VERBOSE DebugStub.Print( "tick scale = {0} (actual {1:d}, fast {2:d})", __arglist(this.tickScale, cps, ((1 << tickRoll) * KernelTicksPerSecond / (ulong)this.tickScale)) ); #endif // VERBOSE return true; } return false; } internal void SetNoisyTimer(bool noisy) { this.noisyTimer = noisy; } [NoHeapAllocation] private long GetKernelTicksFromHW() { bool iflag = Processor.DisableInterrupts(); try { ClockLogger.AddEntry(100, rps, timer); long r = rps.GetKernelTicks(timer.Timer2Read()); return r; } finally { Processor.RestoreInterrupts(iflag); } } [NoHeapAllocation] private long GetKernelTicksFromTsc() { long now = unchecked((long)Processor.CycleCount); long tscDelta = now - this.tscSnapshot; long tickDelta = (tscDelta * tickScale) >> tickRoll; DebugStub.Assert(tickDelta >= 0); return ticksSnapshot + tickDelta; } [NoHeapAllocation] private long InternalGetKernelTicks() { if (!initedFastTime) { initedFastTime = InitializeFastTime(); } if (this.noisyTimer || !initedFastTime) { // Looks like Virtual PC or one not suited to // fast time calculation. Always pull time from // the hardware as the TSC is not virtualized // and is not to be trusted. return GetKernelTicksFromHW(); } else { if (!tscSnapshotValid) { // When waking up from a processor halt // event or from a clock interrupt, sync // with the fixed h/w's concept of time. this.ticksSnapshot = GetKernelTicksFromHW(); this.tscSnapshot = unchecked((long)Processor.CycleCount); this.tscSnapshotValid = true; return ticksSnapshot; } else { return GetKernelTicksFromTsc(); } } } [NoHeapAllocation] public override long GetKernelTicks() { #if DEBUG_TIMER { long cpuT = GetKernelTicksFromTsc(); long hwT = GetKernelTicksFromHW(); if (cpuT - hwT > 5000 || hwT - cpuT < -5000) { DebugStub.Print("Delta = {0:d} (hw {1:d} cpu {2:d})\n", __arglist(cpuT - hwT, hwT, cpuT)); } } #endif // DEBUG_TIMER // We use the TSC to provide fast kernel ticks as // far as possible, and periodically sync against an // off-chip clock. Because there are different time // sources and we don't ever want to report time // going backwards, we need to check that time would // reported to have advanced or remained the same, // and that the clock has not wrapped. long kernelTicks = InternalGetKernelTicks(); if (kernelTicks >= lastKernelTicks) { lastKernelTicks = kernelTicks; return kernelTicks; } else if (kernelTicks < 0 && lastKernelTicks > 0) { // clock wrap return kernelTicks; } #if DEBUG_TIMER // Skew from switching between tsc and underlying clock DebugStub.Print("Backwards delta = {0} (HW {1} TSC {2})\n", __arglist(kernelTicks - lastKernelTicks, GetKernelTicksFromHW(), GetKernelTicksFromTsc()) ); #endif // DEBUG_TIMER return lastKernelTicks; } [NoHeapAllocation] public override void CpuResumeFromHaltEvent() { tscSnapshotValid = false; } [NoHeapAllocation] public override long GetRtcTime() { return rtcBootTime + GetKernelTicks(); } public override void SetRtcTime(long rtcTime) { long delta = rtcTime - GetRtcTime(); rtcBootTime = rtcBootTime + delta; PushRtcTime(new DateTime(rtcTime)); } [NoHeapAllocation] internal static byte BcdToHex(byte bcd) { return (byte) (10 * (bcd >> 4) + (bcd & 0x0f)); } [NoHeapAllocation] internal static byte HexToBcd(int hex) { int h = hex / 10; return (byte) ((h << 4) | (hex - h * 10)); } internal bool UpdateInProgress() { return (ReadRtc(DS1287_A) & DS1287_A_UIP) == DS1287_A_UIP; } private DateTime PullRtcTime() { for (uint iters = 0; iters < maxAttempts; iters++) { bool iflag = Processor.DisableInterrupts(); try { ReadRtc(DS1287_C); // Clear update flag if set if ((ReadRtc(DS1287_A) & DS1287_A_UIP) != 0) { // Update is in progress, try again continue; } // There is no update // pending. The specs suggest at least // ~244us to read values (T_BUC)... byte second = BcdToHex(ReadRtc(DS1287_SECONDS)); byte minute = BcdToHex(ReadRtc(DS1287_MINUTES)); byte hour = BcdToHex(ReadRtc(DS1287_HOURS)); byte day = BcdToHex(ReadRtc(DS1287_DAY_OF_MONTH)); byte month = BcdToHex(ReadRtc(DS1287_MONTH)); byte year = BcdToHex(ReadRtc(DS1287_YEAR)); byte century = BcdToHex(ReadRtc(DS1287_USERDATA)); // ...but we don't // trust the specs, particularly as we may // be running on emulated hardware. Retry // if an update is pending or appears to // have completed whilst reading the time. if ((ReadRtc(DS1287_A) & DS1287_A_UIP) != 0 || (ReadRtc(DS1287_C) & DS1287_C_UF) != 0) { continue; } DebugStub.Print("PullRtcTime: " + "{0:d4}-{1:d2}-{2:d} {3:2}:{4:d2}:{5:d2}" + "\n", __arglist(100*century+year, month, day, hour, minute, second)); // There appears to be an issue with the timer that // doesn't clear/zero out the most significant digit // when a value can be represented by a single digit. // Attempt to recover from this scenario. second = (second < 60) ? second : (byte) (second % 10); minute = (minute < 60) ? minute : (byte) (minute % 10); hour = (hour < 24) ? hour : (byte) (hour % 10); day = (day <= 31) ? day : (byte) (day % 10); month = (month <= 12) ? month : (byte) (month % 10); year = (year < 100) ? year : (byte) (year % 100); return new DateTime(century * 100 + year, month, day, hour, minute, second); } catch (ArgumentOutOfRangeException e) { DebugStub.Print("PullRtcTime failed: {0} "+ "(1:x}:{2:x}:{3:x} {4:x}/{5:x} "+ "100*{6:x}+{7:x})\n", __arglist(e, ReadRtc(DS1287_HOURS), ReadRtc(DS1287_MINUTES), ReadRtc(DS1287_SECONDS), ReadRtc(DS1287_MONTH), ReadRtc(DS1287_DAY_OF_MONTH), ReadRtc(DS1287_USERDATA), ReadRtc(DS1287_YEAR))); if (iters > (maxAttempts >> 1)) { DebugStub.Break(); } } finally { Processor.RestoreInterrupts(iflag); } } return new DateTime(2005, 1, 1, 0, 0, 0); } private void PushRtcTime(DateTime dt) { bool iflag = Processor.DisableInterrupts(); byte saved = ReadRtc(DS1287_B); try { // Set UTI bit to stop transfers between RTC // bytes and user buffer. WriteRtc(DS1287_B, (byte)(saved | DS1287_B_UTI)); // Write new values WriteRtc(DS1287_SECONDS, HexToBcd(dt.Second)); WriteRtc(DS1287_MINUTES, HexToBcd(dt.Minute)); WriteRtc(DS1287_HOURS, HexToBcd(dt.Hour)); WriteRtc(DS1287_DAY_OF_MONTH, HexToBcd(dt.Day)); WriteRtc(DS1287_MONTH, HexToBcd(dt.Month)); int century = dt.Year / 100; WriteRtc(9, HexToBcd(dt.Year - century * 100)); WriteRtc(DS1287_YEAR, HexToBcd(dt.Year - century*100)); WriteRtc(DS1287_USERDATA, HexToBcd(century)); // Clear UTI bit to enable transfers again DebugStub.Print("PushRtcTime {3:2}:{4:d2}:{5:d2} {0}/{1}-{2:d4}\n", __arglist(dt.Month, dt.Day, 100*century + dt.Year, dt.Hour, dt.Minute, dt.Second)); } finally { WriteRtc(DS1287_B, saved); Processor.RestoreInterrupts(iflag); } } } }