/////////////////////////////////////////////////////////////////////////////// // // 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 // http://www.cebix.net/downloads/bebox/bq3285.pdf // //#define SAMPLE_PC using Microsoft.Singularity.Io; using System; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Threading; using Microsoft.Singularity.Configuration; namespace Microsoft.Singularity.Hal { [DriverCategory] [Signature("/pnp/PNP0B00")] public sealed class RtcResources : DriverCategoryDeclaration { [IoPortRange(0, Default = 0x0070, 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 MC146818A. It provides timing resolution /// of 1 second, but can generate periodic interrupts /// more frequently. /// [ CLSCompliant(false) ] public sealed class RTClock { // 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 private PnpConfig config; private IoPort rtcadd; private IoPort rtcdat; private Apic apic; private byte irq; private byte interrupt; internal volatile uint irqCount = 0; private SpinLock rtcSpinLock; // Protects rtcBootTime and rtc update private long rtcBootTime; // Constructor internal RTClock(PnpConfig config, Apic apic) { 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.irq = ((IoIrqRange)config.DynamicRanges[1]).Irq; this.apic = apic; this.rtcSpinLock = new SpinLock(); rtcadd = ((IoPortRange)config.DynamicRanges[0]) .PortAtOffset(0, 1, Access.ReadWrite); rtcdat = ((IoPortRange)config.DynamicRanges[0]) .PortAtOffset(1, 1, Access.ReadWrite); this.interrupt = Initialize(); } [NoHeapAllocation] private byte ReadRtc(byte addr) { IoResult result; result = rtcadd.Write8NoThrow(addr); DebugStub.Assert(IoResult.Success == result); byte value; 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); } private byte Initialize() { // Disable and clear interrupts if (Processor.SamplingEnabled()) { WriteRtc(DS1287_A, DS1287_A_DIVON | DS1287_A_8KHZ); } else { WriteRtc(DS1287_A, DS1287_A_DIVON | DS1287_A_64HZ); } WriteRtc(DS1287_B, DS1287_B_HF_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(); DebugStub.Print("RTClock::Start()\n"); // Enable and clear interrupts // NB it may take 500ms for first interrupt to be generated. WriteRtc(DS1287_A, DS1287_A_DIVON | DS1287_A_2HZ); WriteRtc(DS1287_B, DS1287_B_HF_24H | DS1287_B_DF_BCD | DS1287_B_PIE | DS1287_B_SQWE); ReadRtc(DS1287_C); apic.EnableIrq(irq); return apic.IrqToInterrupt(irq); } private int Log2(uint value) { int l = 0; if ((value & 0xffff0000u) != 0) { l += 16; value >>= 16; } if ((value & 0x0000ff00u) != 0) { l += 8; value >>= 8; } if ((value & 0x000000f0u) != 0) { l += 4; value >>= 4; } if ((value & 0x0000000cu) != 0) { l += 2; value >>= 2; } if ((value & 0x0000000au) != 0) { l += 1; value >>= 1; } return l; } internal bool SetFrequency(uint frequency) { if (frequency > 8192 || frequency < 2) { return false; } if (Processor.SamplingEnabled()) { frequency = 8192; } int a = 0x20; a |= (0xf - (Log2(frequency) - 1)) & 0xf; WriteRtc(DS1287_A, (byte) a); WriteRtc(DS1287_B, DS1287_B_HF_24H | DS1287_B_DF_BCD | DS1287_B_PIE | DS1287_B_SQWE); ReadRtc(DS1287_C); return true; } public void Finalize() { apic.DisableIrq(irq); } [NoHeapAllocation] public void ClearInterrupt() { byte status = ReadRtc(DS1287_C); if ((status & DS1287_C_PF) != 0) { irqCount++; } apic.AckIrq(irq); } public byte Interrupt { [NoHeapAllocation] get { return interrupt; } } /////////////////////////////////////////////////////////////////////// [ System.Diagnostics.Conditional("SINGULARITY_MP") ] [NoHeapAllocation] private void AcquireLock() { rtcSpinLock.Acquire(); } [ System.Diagnostics.Conditional("SINGULARITY_MP") ] [NoHeapAllocation] private void ReleaseLock() { rtcSpinLock.Release(); } [NoHeapAllocation] public long GetBootTime() { bool en = Processor.DisableInterrupts(); try { AcquireLock(); long bootTime = rtcBootTime; ReleaseLock(); return bootTime; } finally { Processor.RestoreInterrupts(en); } } public void SetRtcTime(long newRtcTime, long kernelTicks) { bool en = Processor.DisableInterrupts(); try { AcquireLock(); long currentRtcTime = rtcBootTime + kernelTicks; long delta = newRtcTime - currentRtcTime; rtcBootTime = rtcBootTime + delta; PushRtcTime(newRtcTime); ReleaseLock(); } finally { Processor.RestoreInterrupts(en); } } [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)); } private long PullRtcTime() { for (uint iters = 0; iters < 1000000; 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)); DateTime d = new DateTime(century * 100 + year, month, day, hour, minute, second); return d.Ticks; } catch (ArgumentOutOfRangeException e) { DebugStub.Print("PullRtcTime failed: {0}\n", __arglist(e)); } finally { Processor.RestoreInterrupts(iflag); } } return (new DateTime(2005, 1, 1, 0, 0, 0)).Ticks; } private void PushRtcTime(long dtTicks) { DateTime dt = new DateTime(dtTicks); bool iflag = Processor.DisableInterrupts(); try { byte saved = ReadRtc(DS1287_B); // 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(DS1287_YEAR, HexToBcd(dt.Year - century*100)); WriteRtc(DS1287_USERDATA, HexToBcd(century)); // Clear UTI bit to enable transfers again WriteRtc(DS1287_B, saved); 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 { Processor.RestoreInterrupts(iflag); } } } }