using System.Runtime.InteropServices;
namespace XFESystem.NativeKernel;
// Live CMOS RTC, not a compile timestamp or host clock. The virtual-machine
// launch must configure RTC base=utc. Physical firmware RTC timezone conventions
// require separate validation; CMOS itself contains no trustworthy timezone.
public static unsafe class UtcClock
{
const uint PollLimit = 100000;
static byte ReadRegister(byte index, byte nmiMask)
{
Native.Out8(0x70, (byte)(nmiMask | index));
return Native.In8(0x71);
}
static bool Sample(byte nmiMask, out ulong value)
{
value = 0;
bool stable = false;
for (uint poll = 0; poll < PollLimit; poll++)
{
byte status = ReadRegister(0x0a, nmiMask);
if (status == 255) return false;
if ((status & 0x80) == 0) { stable = true; break; }
Native.Pause();
}
if (!stable || (ReadRegister(0x0d, nmiMask) & 0x80) == 0) return false;
value = ReadRegister(0, nmiMask);
value |= (ulong)ReadRegister(2, nmiMask) << 8;
value |= (ulong)ReadRegister(4, nmiMask) << 16;
value |= (ulong)ReadRegister(7, nmiMask) << 24;
value |= (ulong)ReadRegister(8, nmiMask) << 32;
value |= (ulong)ReadRegister(9, nmiMask) << 40;
value |= (ulong)ReadRegister(0x32, nmiMask) << 48; // Conventional PC century; invalid/unset values use documented pivot.
value |= (ulong)ReadRegister(0x0b, nmiMask) << 56;
return (ReadRegister(0x0a, nmiMask) & 0x80) == 0;
}
static uint Decode(byte value, bool binary)
{
if (binary) return value;
if ((value & 15) > 9 || (value >> 4) > 9) return 0xffffffff;
return (uint)((value >> 4) * 10 + (value & 15));
}
static bool Leap(uint year) => year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
static uint DaysInMonth(uint year, uint month)
{
if (month == 2) return Leap(year) ? 29u : 28u;
if (month == 4 || month == 6 || month == 9 || month == 11) return 30;
return 31;
}
static ulong DaysBeforeYear(uint year)
{
ulong y = year - 1;
return 365 * y + y / 4 - y / 100 + y / 400;
}
static ulong Convert(ulong value)
{
byte status = (byte)(value >> 56);
if (status == 255 || (status & 0x80) != 0) return 0; // SET inhibits clock updates; never label a frozen RTC as live.
bool binary = (status & 4) != 0, twentyFour = (status & 2) != 0;
uint second = Decode((byte)value, binary), minute = Decode((byte)(value >> 8), binary);
byte rawHour = (byte)(value >> 16);
uint hour = Decode((byte)(rawHour & 0x7f), binary);
if (!twentyFour)
{
if (hour < 1 || hour > 12) return 0;
hour = hour % 12 + ((rawHour & 0x80) != 0 ? 12u : 0u);
}
else if ((rawHour & 0x80) != 0) return 0;
uint day = Decode((byte)(value >> 24), binary), month = Decode((byte)(value >> 32), binary);
uint shortYear = Decode((byte)(value >> 40), binary), century = Decode((byte)(value >> 48), binary);
if (second > 59 || minute > 59 || hour > 23 || month < 1 || month > 12 || shortYear > 99) return 0;
uint year = century >= 19 && century <= 99 ? century * 100 + shortYear : shortYear < 70 ? 2000 + shortYear : 1900 + shortYear;
if (year < 1601 || year > 9999 || day < 1 || day > DaysInMonth(year, month)) return 0;
ulong days = DaysBeforeYear(year) - DaysBeforeYear(1601);
for (uint m = 1; m < month; m++) days += DaysInMonth(year, m);
days += day - 1;
return (days * 86400 + hour * 3600UL + minute * 60UL + second) * 10000000;
}
public static ulong ReadFileTime()
{
ulong flags = Native.IrqSave();
byte originalSelector = Native.In8(0x70), nmiMask = (byte)(originalSelector & 0x80);
ulong result = 0;
for (uint retry = 0; retry < 8; retry++)
if (Sample(nmiMask, out ulong first) && Sample(nmiMask, out ulong second) && first == second)
{ result = Convert(second); break; }
Native.Out8(0x70, originalSelector);
Native.IrqRestore(flags);
return result;
}
[UnmanagedCallersOnly(EntryPoint = "xfe_runtime_utc_filetime")]
public static ulong FileTime() => ReadFileTime();
}
using System.Runtime.InteropServices;
namespace XFESystem.NativeKernel;
// Live CMOS RTC, not a compile timestamp or host clock. The virtual-machine
// launch must configure RTC base=utc. Physical firmware RTC timezone conventions
// require separate validation; CMOS itself contains no trustworthy timezone.
public static unsafe class UtcClock
{
const uint PollLimit = 100000;
static byte ReadRegister(byte index, byte nmiMask)
{
Native.Out8(0x70, (byte)(nmiMask | index));
return Native.In8(0x71);
}
static bool Sample(byte nmiMask, out ulong value)
{
value = 0;
bool stable = false;
for (uint poll = 0; poll < PollLimit; poll++)
{
byte status = ReadRegister(0x0a, nmiMask);
if (status == 255) return false;
if ((status & 0x80) == 0) { stable = true; break; }
Native.Pause();
}
if (!stable || (ReadRegister(0x0d, nmiMask) & 0x80) == 0) return false;
value = ReadRegister(0, nmiMask);
value |= (ulong)ReadRegister(2, nmiMask) << 8;
value |= (ulong)ReadRegister(4, nmiMask) << 16;
value |= (ulong)ReadRegister(7, nmiMask) << 24;
value |= (ulong)ReadRegister(8, nmiMask) << 32;
value |= (ulong)ReadRegister(9, nmiMask) << 40;
value |= (ulong)ReadRegister(0x32, nmiMask) << 48; // Conventional PC century; invalid/unset values use documented pivot.
value |= (ulong)ReadRegister(0x0b, nmiMask) << 56;
return (ReadRegister(0x0a, nmiMask) & 0x80) == 0;
}
static uint Decode(byte value, bool binary)
{
if (binary) return value;
if ((value & 15) > 9 || (value >> 4) > 9) return 0xffffffff;
return (uint)((value >> 4) * 10 + (value & 15));
}
static bool Leap(uint year) => year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
static uint DaysInMonth(uint year, uint month)
{
if (month == 2) return Leap(year) ? 29u : 28u;
if (month == 4 || month == 6 || month == 9 || month == 11) return 30;
return 31;
}
static ulong DaysBeforeYear(uint year)
{
ulong y = year - 1;
return 365 * y + y / 4 - y / 100 + y / 400;
}
static ulong Convert(ulong value)
{
byte status = (byte)(value >> 56);
if (status == 255 || (status & 0x80) != 0) return 0; // SET inhibits clock updates; never label a frozen RTC as live.
bool binary = (status & 4) != 0, twentyFour = (status & 2) != 0;
uint second = Decode((byte)value, binary), minute = Decode((byte)(value >> 8), binary);
byte rawHour = (byte)(value >> 16);
uint hour = Decode((byte)(rawHour & 0x7f), binary);
if (!twentyFour)
{
if (hour < 1 || hour > 12) return 0;
hour = hour % 12 + ((rawHour & 0x80) != 0 ? 12u : 0u);
}
else if ((rawHour & 0x80) != 0) return 0;
uint day = Decode((byte)(value >> 24), binary), month = Decode((byte)(value >> 32), binary);
uint shortYear = Decode((byte)(value >> 40), binary), century = Decode((byte)(value >> 48), binary);
if (second > 59 || minute > 59 || hour > 23 || month < 1 || month > 12 || shortYear > 99) return 0;
uint year = century >= 19 && century <= 99 ? century * 100 + shortYear : shortYear < 70 ? 2000 + shortYear : 1900 + shortYear;
if (year < 1601 || year > 9999 || day < 1 || day > DaysInMonth(year, month)) return 0;
ulong days = DaysBeforeYear(year) - DaysBeforeYear(1601);
for (uint m = 1; m < month; m++) days += DaysInMonth(year, m);
days += day - 1;
return (days * 86400 + hour * 3600UL + minute * 60UL + second) * 10000000;
}
public static ulong ReadFileTime()
{
ulong flags = Native.IrqSave();
byte originalSelector = Native.In8(0x70), nmiMask = (byte)(originalSelector & 0x80);
ulong result = 0;
for (uint retry = 0; retry < 8; retry++)
if (Sample(nmiMask, out ulong first) && Sample(nmiMask, out ulong second) && first == second)
{ result = Convert(second); break; }
Native.Out8(0x70, originalSelector);
Native.IrqRestore(flags);
return result;
}
[UnmanagedCallersOnly(EntryPoint = "xfe_runtime_utc_filetime")]
public static ulong FileTime() => ReadFileTime();
}