2008-03-05 09:52:00 -05:00
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <remarks>
|
|
|
|
/// Class <c>RTClock</c> 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.
|
|
|
|
/// </remarks>
|
|
|
|
[ CLSCompliant(false) ]
|
2008-11-17 18:29:00 -05:00
|
|
|
public sealed class RTClockApic
|
2008-03-05 09:52:00 -05:00
|
|
|
{
|
|
|
|
// 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;
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
private byte firstStatus;
|
|
|
|
|
2008-03-05 09:52:00 -05:00
|
|
|
// Constructor
|
2008-11-17 18:29:00 -05:00
|
|
|
internal RTClockApic(PnpConfig config, Apic apic)
|
2008-03-05 09:52:00 -05:00
|
|
|
{
|
2008-11-17 18:29:00 -05:00
|
|
|
|
|
|
|
int cpuId;
|
|
|
|
Microsoft.Singularity.Hal.Platform p = Microsoft.Singularity.Hal.Platform.ThePlatform;
|
|
|
|
cpuId = p.ApicId;
|
|
|
|
|
2008-03-05 09:52:00 -05:00
|
|
|
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;
|
2008-11-17 18:29:00 -05:00
|
|
|
this.rtcSpinLock = new SpinLock(SpinLock.Types.RTClock);
|
2008-03-05 09:52:00 -05:00
|
|
|
|
|
|
|
rtcadd = ((IoPortRange)config.DynamicRanges[0])
|
|
|
|
.PortAtOffset(0, 1, Access.ReadWrite);
|
|
|
|
rtcdat = ((IoPortRange)config.DynamicRanges[0])
|
|
|
|
.PortAtOffset(1, 1, Access.ReadWrite);
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
if (cpuId == 0) {
|
|
|
|
this.interrupt = Initialize();
|
|
|
|
DebugStub.Print("RTClock: interrupt {0:x2}\n",
|
|
|
|
__arglist(this.interrupt));
|
|
|
|
}
|
2008-03-05 09:52:00 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
[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()
|
|
|
|
{
|
2008-11-17 18:29:00 -05:00
|
|
|
if ((ReadRtc(DS1287_D) & DS1287_D_VRT) == 0) {
|
2008-03-05 09:52:00 -05:00
|
|
|
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.
|
2008-11-17 18:29:00 -05:00
|
|
|
|
|
|
|
WriteRtc(DS1287_B, DS1287_B_UTI);
|
|
|
|
WriteRtc(DS1287_A, DS1287_A_2HZ);
|
2008-03-05 09:52:00 -05:00
|
|
|
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);
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
// apic.SetIrqPriority(irq, IrqPriority.High);
|
2008-03-05 09:52:00 -05:00
|
|
|
apic.EnableIrq(irq);
|
2008-11-17 18:29:00 -05:00
|
|
|
|
|
|
|
// Here we wait for the first interrupt to be
|
|
|
|
// raised, which might be upto 0.5 seconds. This
|
|
|
|
// wait is based on experience, ie not waiting with
|
|
|
|
// some boxes (the infamous Ryan's TPM machine for
|
|
|
|
// instance) leads to the clock interrupt never being
|
|
|
|
// raised.
|
|
|
|
|
|
|
|
while ((ReadRtc(DS1287_C) & DS1287_C_PF) != DS1287_C_PF) {
|
|
|
|
// TODO: Break out after 1 second and
|
|
|
|
// report an error.
|
|
|
|
}
|
|
|
|
firstStatus = DS1287_C_PF;
|
|
|
|
|
|
|
|
byte ivValue = apic.IrqToInterrupt(irq);
|
|
|
|
|
|
|
|
return ivValue;
|
2008-03-05 09:52:00 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
private int Log2(uint value)
|
|
|
|
{
|
|
|
|
int l = 0;
|
2008-11-17 18:29:00 -05:00
|
|
|
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;
|
|
|
|
}
|
2008-03-05 09:52:00 -05:00
|
|
|
return l;
|
|
|
|
}
|
|
|
|
|
|
|
|
internal bool SetFrequency(uint frequency)
|
|
|
|
{
|
2008-11-17 18:29:00 -05:00
|
|
|
if (frequency > 8192 || frequency < 2) {
|
2008-03-05 09:52:00 -05:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Processor.SamplingEnabled()) {
|
|
|
|
frequency = 8192;
|
|
|
|
}
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
int a = DS1287_A_DIVON;
|
2008-03-05 09:52:00 -05:00
|
|
|
a |= (0xf - (Log2(frequency) - 1)) & 0xf;
|
2008-11-17 18:29:00 -05:00
|
|
|
DebugStub.Assert((a & 0xf0) == DS1287_A_DIVON);
|
|
|
|
|
2008-03-05 09:52:00 -05:00
|
|
|
WriteRtc(DS1287_A, (byte) a);
|
|
|
|
WriteRtc(DS1287_B, DS1287_B_HF_24H | DS1287_B_DF_BCD | DS1287_B_PIE | DS1287_B_SQWE);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
public void ReleaseResources()
|
2008-03-05 09:52:00 -05:00
|
|
|
{
|
|
|
|
apic.DisableIrq(irq);
|
|
|
|
}
|
|
|
|
|
|
|
|
[NoHeapAllocation]
|
|
|
|
public void ClearInterrupt()
|
|
|
|
{
|
|
|
|
byte status = ReadRtc(DS1287_C);
|
2008-11-17 18:29:00 -05:00
|
|
|
status |= firstStatus;
|
|
|
|
firstStatus = 0;
|
|
|
|
|
|
|
|
const byte CMask = DS1287_C_INTF | DS1287_C_PF;
|
|
|
|
|
|
|
|
DebugStub.Assert((status & CMask) == CMask);
|
|
|
|
|
|
|
|
if ((status & DS1287_C_PF) != 0) {
|
|
|
|
this.irqCount++;
|
2008-03-05 09:52:00 -05:00
|
|
|
}
|
2008-11-17 18:29:00 -05:00
|
|
|
apic.AckIrq(apic.InterruptToIrq(this.interrupt));
|
2008-03-05 09:52:00 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
public byte Interrupt
|
|
|
|
{
|
|
|
|
[NoHeapAllocation]
|
2008-11-17 18:29:00 -05:00
|
|
|
get { return this.interrupt; }
|
2008-03-05 09:52:00 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
[ 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 {
|
|
|
|
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.
|
2008-11-17 18:29:00 -05:00
|
|
|
if ((ReadRtc(DS1287_A) & DS1287_A_UIP) != 0) {
|
2008-03-05 09:52:00 -05:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|