557 lines
20 KiB
C
557 lines
20 KiB
C
// Kernel wall-clock service. This file owns boot-time conversion, timezone policy, and current-time queries.
|
|
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
#include <ntlite/status.h>
|
|
#include <ntlite/boot_info.h>
|
|
#include <kernel/hal/hal_api.h>
|
|
#include <kernel/ke_time.h>
|
|
#include <kernel/ke_debug.h>
|
|
#include <kernel/ke_settings.h>
|
|
|
|
#define KE_TIME_FALLBACK_YEAR 2024u
|
|
#define KE_TIME_FALLBACK_MONTH 1u
|
|
#define KE_TIME_FALLBACK_DAY 1u
|
|
|
|
#define KE_TZ_UNSPECIFIED SYS_TIME_UNSPECIFIED_TZ
|
|
#define KE_CE_STANDARD_OFFSET 60
|
|
#define KE_DST_OFFSET_DELTA 60
|
|
|
|
typedef enum KE_DST_RULE {
|
|
KeDstRuleNone = 0,
|
|
KeDstRuleAlways,
|
|
KeDstRuleCentralEurope,
|
|
} KE_DST_RULE;
|
|
|
|
typedef struct KE_TIME_ZONE_POLICY {
|
|
int16_t StandardOffsetMinutes; // minutes east of UTC.
|
|
KE_DST_RULE DstRule;
|
|
bool TimeZoneKnown;
|
|
} KE_TIME_ZONE_POLICY;
|
|
|
|
typedef enum KE_HARDWARE_CLOCK_MODE {
|
|
KeHardwareClockUtc = 0,
|
|
KeHardwareClockLocal,
|
|
} KE_HARDWARE_CLOCK_MODE;
|
|
|
|
static const HAL_API* gTimeHal;
|
|
static uint64_t gSourceRawEpochSeconds;
|
|
static int16_t gSourceTimeZoneMinutes;
|
|
static bool gSourceTimeZoneKnown;
|
|
static uint32_t gSourceNanosecond;
|
|
static uint64_t gSourceTickMicroseconds;
|
|
static uint64_t gBaseUtcEpochSeconds;
|
|
static uint64_t gBaseTickMicroseconds;
|
|
static uint16_t gSourceFlags;
|
|
static KE_TIME_ZONE_POLICY gTimeZonePolicy = {
|
|
KE_CE_STANDARD_OFFSET,
|
|
KeDstRuleCentralEurope,
|
|
true,
|
|
};
|
|
static KE_HARDWARE_CLOCK_MODE gHardwareClockMode = KeHardwareClockUtc;
|
|
|
|
// Checks whether leap Year.
|
|
static bool KiIsLeapYear(uint32_t year) {
|
|
return ((year % 4u) == 0u && (year % 100u) != 0u) || ((year % 400u) == 0u);
|
|
}
|
|
|
|
// Implements the days In Month operation.
|
|
static uint32_t KiDaysInMonth(uint32_t year, uint32_t month) {
|
|
static const uint8_t days[12] = { 31,28,31,30,31,30,31,31,30,31,30,31 };
|
|
if (month == 2u && KiIsLeapYear(year)) return 29u;
|
|
if (month < 1u || month > 12u) return 0u;
|
|
return days[month - 1u];
|
|
}
|
|
|
|
static bool KiValidCalendar(uint32_t year, uint32_t month, uint32_t day,
|
|
uint32_t hour, uint32_t minute, uint32_t second,
|
|
uint32_t nanosecond) {
|
|
if (year < 1970u || year > 9999u) return false;
|
|
if (month < 1u || month > 12u) return false;
|
|
uint32_t dim = KiDaysInMonth(year, month);
|
|
if (day < 1u || day > dim) return false;
|
|
if (hour > 23u || minute > 59u || second > 59u) return false;
|
|
if (nanosecond >= 1000000000u) return false;
|
|
return true;
|
|
}
|
|
|
|
// Implements the days Before Year operation.
|
|
static uint64_t KiDaysBeforeYear(uint32_t year) {
|
|
uint64_t days = 0;
|
|
for (uint32_t y = 1970u; y < year; y++)
|
|
days += KiIsLeapYear(y) ? 366u : 365u;
|
|
return days;
|
|
}
|
|
|
|
static uint64_t KiCalendarToEpochSeconds(uint32_t year, uint32_t month, uint32_t day,
|
|
uint32_t hour, uint32_t minute, uint32_t second) {
|
|
uint64_t days = KiDaysBeforeYear(year);
|
|
for (uint32_t m = 1u; m < month; m++)
|
|
days += KiDaysInMonth(year, m);
|
|
days += (uint64_t)(day - 1u);
|
|
return (((days * 24u) + hour) * 60u + minute) * 60u + second;
|
|
}
|
|
|
|
// Implements the epoch Seconds To Calendar operation.
|
|
static void KiEpochSecondsToCalendar(uint64_t epochSeconds, SYS_SYSTEM_TIME* out) {
|
|
uint64_t days = epochSeconds / 86400u;
|
|
uint32_t sod = (uint32_t)(epochSeconds % 86400u);
|
|
|
|
uint32_t year = 1970u;
|
|
while (true) {
|
|
uint32_t diy = KiIsLeapYear(year) ? 366u : 365u;
|
|
if (days < diy) break;
|
|
days -= diy;
|
|
year++;
|
|
}
|
|
|
|
uint32_t month = 1u;
|
|
while (true) {
|
|
uint32_t dim = KiDaysInMonth(year, month);
|
|
if (days < dim) break;
|
|
days -= dim;
|
|
month++;
|
|
}
|
|
|
|
uint64_t totalDays = epochSeconds / 86400u;
|
|
out->Year = (uint16_t)year;
|
|
out->Month = (uint8_t)month;
|
|
out->Day = (uint8_t)(days + 1u);
|
|
out->Hour = (uint8_t)(sod / 3600u);
|
|
out->Minute = (uint8_t)((sod / 60u) % 60u);
|
|
out->Second = (uint8_t)(sod % 60u);
|
|
out->DayOfWeek = (uint8_t)((totalDays + 4u) % 7u); // 1970-01-01 was Thursday.
|
|
}
|
|
|
|
// Implements the day Of Week operation.
|
|
static uint32_t KiDayOfWeek(uint32_t year, uint32_t month, uint32_t day) {
|
|
uint64_t epoch = KiCalendarToEpochSeconds(year, month, day, 0, 0, 0);
|
|
return (uint32_t)(((epoch / 86400u) + 4u) % 7u);
|
|
}
|
|
|
|
// Implements the last Sunday Of Month operation.
|
|
static uint32_t KiLastSundayOfMonth(uint32_t year, uint32_t month) {
|
|
uint32_t last = KiDaysInMonth(year, month);
|
|
uint32_t dow = KiDayOfWeek(year, month, last); // Sunday == 0.
|
|
return last - dow;
|
|
}
|
|
|
|
// Implements the central Europe Dst Active Utc operation.
|
|
static bool KiCentralEuropeDstActiveUtc(uint64_t utcEpochSeconds) {
|
|
SYS_SYSTEM_TIME utc;
|
|
memset(&utc, 0, sizeof(utc));
|
|
KiEpochSecondsToCalendar(utcEpochSeconds, &utc);
|
|
|
|
uint32_t startDay = KiLastSundayOfMonth(utc.Year, 3u);
|
|
uint32_t endDay = KiLastSundayOfMonth(utc.Year, 10u);
|
|
uint64_t startUtc = KiCalendarToEpochSeconds(utc.Year, 3u, startDay, 1u, 0u, 0u);
|
|
uint64_t endUtc = KiCalendarToEpochSeconds(utc.Year, 10u, endDay, 1u, 0u, 0u);
|
|
return utcEpochSeconds >= startUtc && utcEpochSeconds < endUtc;
|
|
}
|
|
|
|
// Implements the offset For Utc Epoch operation.
|
|
static int16_t KiOffsetForUtcEpoch(uint64_t utcEpochSeconds, bool* daylight) {
|
|
int32_t offset = gTimeZonePolicy.StandardOffsetMinutes;
|
|
bool dst = false;
|
|
|
|
if (gTimeZonePolicy.DstRule == KeDstRuleAlways) {
|
|
dst = true;
|
|
} else if (gTimeZonePolicy.DstRule == KeDstRuleCentralEurope) {
|
|
dst = KiCentralEuropeDstActiveUtc(utcEpochSeconds);
|
|
}
|
|
|
|
if (dst) offset += KE_DST_OFFSET_DELTA;
|
|
if (daylight) *daylight = dst;
|
|
return (int16_t)offset;
|
|
}
|
|
|
|
// Implements the local Epoch To Utc operation.
|
|
static uint64_t KiLocalEpochToUtc(uint64_t localEpochSeconds, int16_t offsetMinutes) {
|
|
uint64_t offsetSeconds = (uint64_t)((offsetMinutes < 0) ? -offsetMinutes : offsetMinutes) * 60u;
|
|
if (offsetMinutes >= 0)
|
|
return (localEpochSeconds > offsetSeconds) ? (localEpochSeconds - offsetSeconds) : 0;
|
|
return localEpochSeconds + offsetSeconds;
|
|
}
|
|
|
|
// Implements the rebase Utc From Source operation.
|
|
static void KiRebaseUtcFromSource(void) {
|
|
if (gSourceTimeZoneKnown) {
|
|
gBaseUtcEpochSeconds = KiLocalEpochToUtc(gSourceRawEpochSeconds, gSourceTimeZoneMinutes);
|
|
} else if (gHardwareClockMode == KeHardwareClockLocal) {
|
|
bool daylight = false;
|
|
int16_t offset = KiOffsetForUtcEpoch(gSourceRawEpochSeconds, &daylight);
|
|
|
|
// The first pass treated the local clock as UTC only so we can choose
|
|
// the correct rule-year. Run one refinement against the candidate UTC
|
|
// instant to handle daylight-saving transitions cleanly.
|
|
uint64_t candidateUtc = KiLocalEpochToUtc(gSourceRawEpochSeconds, offset);
|
|
offset = KiOffsetForUtcEpoch(candidateUtc, &daylight);
|
|
gBaseUtcEpochSeconds = KiLocalEpochToUtc(gSourceRawEpochSeconds, offset);
|
|
} else {
|
|
gBaseUtcEpochSeconds = gSourceRawEpochSeconds;
|
|
}
|
|
gBaseTickMicroseconds = gSourceTickMicroseconds;
|
|
}
|
|
|
|
// Implements the ascii Lower operation.
|
|
static int KiAsciiLower(int c) {
|
|
if (c >= 'A' && c <= 'Z') return c + ('a' - 'A');
|
|
return c;
|
|
}
|
|
|
|
// Implements the string Equals No Case operation.
|
|
static bool KiStringEqualsNoCase(const char* a, const char* b) {
|
|
if (!a || !b) return false;
|
|
while (*a && *b) {
|
|
if (KiAsciiLower((unsigned char)*a) != KiAsciiLower((unsigned char)*b))
|
|
return false;
|
|
a++;
|
|
b++;
|
|
}
|
|
return *a == '\0' && *b == '\0';
|
|
}
|
|
|
|
// Parses int32.
|
|
static bool KiParseInt32(const char* s, int32_t* out) {
|
|
if (!s || !*s || !out) return false;
|
|
|
|
int sign = 1;
|
|
if (*s == '-') {
|
|
sign = -1;
|
|
s++;
|
|
} else if (*s == '+') {
|
|
s++;
|
|
}
|
|
|
|
if (*s < '0' || *s > '9') return false;
|
|
|
|
int32_t value = 0;
|
|
while (*s >= '0' && *s <= '9') {
|
|
value = (value * 10) + (*s - '0');
|
|
s++;
|
|
}
|
|
if (*s != '\0') return false;
|
|
|
|
*out = value * sign;
|
|
return true;
|
|
}
|
|
|
|
// Implements the starts With No Case operation.
|
|
static bool KiStartsWithNoCase(const char* s, const char* prefix) {
|
|
if (!s || !prefix) return false;
|
|
while (*prefix) {
|
|
if (KiAsciiLower((unsigned char)*s) != KiAsciiLower((unsigned char)*prefix))
|
|
return false;
|
|
s++;
|
|
prefix++;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Parses utc Offset.
|
|
static bool KiParseUtcOffset(const char* s, int16_t* out) {
|
|
if (!s || !out) return false;
|
|
|
|
if (KiStringEqualsNoCase(s, "utc") || KiStringEqualsNoCase(s, "gmt") ||
|
|
// Implements the string Equals No Case operation.
|
|
KiStringEqualsNoCase(s, "z") || KiStringEqualsNoCase(s, "utcz")) {
|
|
*out = 0;
|
|
return true;
|
|
}
|
|
|
|
if (KiStartsWithNoCase(s, "utc")) {
|
|
s += 3;
|
|
} else if (KiStartsWithNoCase(s, "gmt")) {
|
|
s += 3;
|
|
}
|
|
|
|
int sign = 1;
|
|
if (*s == '+') {
|
|
sign = 1;
|
|
s++;
|
|
} else if (*s == '-') {
|
|
sign = -1;
|
|
s++;
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
if (*s < '0' || *s > '9') return false;
|
|
|
|
int hours = 0;
|
|
int digits = 0;
|
|
while (*s >= '0' && *s <= '9' && digits < 2) {
|
|
hours = hours * 10 + (*s - '0');
|
|
s++;
|
|
digits++;
|
|
}
|
|
|
|
int minutes = 0;
|
|
if (*s == ':') {
|
|
s++;
|
|
if (s[0] < '0' || s[0] > '9' || s[1] < '0' || s[1] > '9') return false;
|
|
minutes = (s[0] - '0') * 10 + (s[1] - '0');
|
|
s += 2;
|
|
} else if (*s >= '0' && *s <= '9') {
|
|
// Compact form, e.g. UTC+0200. Exactly two minute digits must remain.
|
|
if (s[1] < '0' || s[1] > '9' || s[2] != '\0') return false;
|
|
minutes = (s[0] - '0') * 10 + (s[1] - '0');
|
|
s += 2;
|
|
}
|
|
|
|
if (*s != '\0') return false;
|
|
if (hours > 14 || minutes > 59) return false;
|
|
|
|
*out = (int16_t)(sign * (hours * 60 + minutes));
|
|
return true;
|
|
}
|
|
|
|
// Implements the apply Default Time Zone Policy operation.
|
|
static void KiApplyDefaultTimeZonePolicy(void) {
|
|
gTimeZonePolicy.StandardOffsetMinutes = KE_CE_STANDARD_OFFSET;
|
|
gTimeZonePolicy.DstRule = KeDstRuleCentralEurope;
|
|
gTimeZonePolicy.TimeZoneKnown = true;
|
|
gHardwareClockMode = KeHardwareClockUtc;
|
|
}
|
|
|
|
// Implements the time Initialize operation.
|
|
void KeTimeInitialize(const BOOT_INFO* bootInfo, const HAL_API* hal) {
|
|
gTimeHal = hal;
|
|
KiApplyDefaultTimeZonePolicy();
|
|
gSourceTickMicroseconds = (hal && hal->Timer.QueryTicks) ? hal->Timer.QueryTicks() : 0;
|
|
|
|
const BOOT_TIME* bt = bootInfo ? &bootInfo->BootTime : 0;
|
|
if (bt && (bt->Flags & BOOT_TIME_FLAG_VALID) &&
|
|
KiValidCalendar(bt->Year, bt->Month, bt->Day, bt->Hour, bt->Minute,
|
|
bt->Second, bt->Nanosecond)) {
|
|
gSourceRawEpochSeconds = KiCalendarToEpochSeconds(bt->Year, bt->Month, bt->Day,
|
|
bt->Hour, bt->Minute, bt->Second);
|
|
gSourceTimeZoneKnown = (bt->Flags & BOOT_TIME_FLAG_TZ_KNOWN) &&
|
|
bt->TimeZoneMinutes != BOOT_TIME_UNSPECIFIED_TZ;
|
|
gSourceTimeZoneMinutes = gSourceTimeZoneKnown ? bt->TimeZoneMinutes : 0;
|
|
gSourceNanosecond = bt->Nanosecond;
|
|
gSourceFlags = SYS_TIME_FLAG_VALID | SYS_TIME_FLAG_UEFI;
|
|
} else {
|
|
HAL_RTC_TIME rtc;
|
|
memset(&rtc, 0, sizeof(rtc));
|
|
if (hal && hal->Rtc.QueryTime && hal->Rtc.QueryTime(&rtc) == NTLSTATUS_SUCCESS &&
|
|
(rtc.Flags & HAL_RTC_TIME_FLAG_VALID) &&
|
|
KiValidCalendar(rtc.Year, rtc.Month, rtc.Day, rtc.Hour, rtc.Minute,
|
|
rtc.Second, rtc.Nanosecond)) {
|
|
gSourceRawEpochSeconds = KiCalendarToEpochSeconds(rtc.Year, rtc.Month, rtc.Day,
|
|
rtc.Hour, rtc.Minute, rtc.Second);
|
|
gSourceTimeZoneKnown = false;
|
|
gSourceTimeZoneMinutes = 0;
|
|
gSourceNanosecond = rtc.Nanosecond;
|
|
gSourceFlags = SYS_TIME_FLAG_VALID | SYS_TIME_FLAG_RTC;
|
|
} else {
|
|
gSourceRawEpochSeconds = KiCalendarToEpochSeconds(KE_TIME_FALLBACK_YEAR,
|
|
KE_TIME_FALLBACK_MONTH,
|
|
KE_TIME_FALLBACK_DAY,
|
|
0, 0, 0);
|
|
gSourceTimeZoneKnown = false;
|
|
gSourceTimeZoneMinutes = 0;
|
|
gSourceNanosecond = 0;
|
|
gSourceFlags = SYS_TIME_FLAG_VALID | SYS_TIME_FLAG_FALLBACK;
|
|
}
|
|
}
|
|
|
|
KiRebaseUtcFromSource();
|
|
|
|
SYS_SYSTEM_TIME now;
|
|
if (KeQuerySystemTime(&now) == NTLSTATUS_SUCCESS) {
|
|
DbgPrint("time: initialized %04u-%02u-%02u %02u:%02u:%02u.%03u flags=0x%x tz=%d hw=%s epoch=%llu\n",
|
|
now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second,
|
|
now.Millisecond, now.Flags, now.TimeZoneMinutes,
|
|
gHardwareClockMode == KeHardwareClockLocal ? "local" : "utc",
|
|
now.EpochSeconds);
|
|
}
|
|
}
|
|
|
|
// Implements the time Reload Settings operation.
|
|
KSTATUS KeTimeReloadSettings(void) {
|
|
KE_TIME_ZONE_POLICY policy = gTimeZonePolicy;
|
|
char value[64];
|
|
bool fixedTimezone = false;
|
|
bool allowStandardOffsetSetting = false;
|
|
|
|
KSTATUS st = KeGetSettingString("time", "timezone", value, sizeof(value));
|
|
if (KSUCCESS(st)) {
|
|
if (KiStringEqualsNoCase(value, "central-europe") ||
|
|
// Implements the string Equals No Case operation.
|
|
KiStringEqualsNoCase(value, "europe/warsaw")) {
|
|
policy.StandardOffsetMinutes = KE_CE_STANDARD_OFFSET;
|
|
policy.DstRule = KeDstRuleCentralEurope;
|
|
policy.TimeZoneKnown = true;
|
|
} else if (KiStringEqualsNoCase(value, "cet")) {
|
|
policy.StandardOffsetMinutes = KE_CE_STANDARD_OFFSET;
|
|
policy.DstRule = KeDstRuleNone;
|
|
policy.TimeZoneKnown = true;
|
|
fixedTimezone = true;
|
|
} else if (KiStringEqualsNoCase(value, "cest")) {
|
|
policy.StandardOffsetMinutes = KE_CE_STANDARD_OFFSET + KE_DST_OFFSET_DELTA;
|
|
policy.DstRule = KeDstRuleNone;
|
|
policy.TimeZoneKnown = true;
|
|
fixedTimezone = true;
|
|
} else if (KiStringEqualsNoCase(value, "custom") ||
|
|
// Implements the string Equals No Case operation.
|
|
KiStringEqualsNoCase(value, "manual")) {
|
|
policy.DstRule = KeDstRuleNone;
|
|
policy.TimeZoneKnown = true;
|
|
fixedTimezone = true;
|
|
allowStandardOffsetSetting = true;
|
|
} else if (KiStringEqualsNoCase(value, "utc") ||
|
|
KiStringEqualsNoCase(value, "gmt") ||
|
|
// Implements the string Equals No Case operation.
|
|
KiStringEqualsNoCase(value, "z")) {
|
|
policy.StandardOffsetMinutes = 0;
|
|
policy.DstRule = KeDstRuleNone;
|
|
policy.TimeZoneKnown = true;
|
|
fixedTimezone = true;
|
|
} else {
|
|
int16_t parsedOffset = 0;
|
|
if (KiParseUtcOffset(value, &parsedOffset)) {
|
|
policy.StandardOffsetMinutes = parsedOffset;
|
|
policy.DstRule = KeDstRuleNone;
|
|
policy.TimeZoneKnown = true;
|
|
fixedTimezone = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
st = KeGetSettingString("time", "standard_offset_minutes", value, sizeof(value));
|
|
if (KSUCCESS(st) && allowStandardOffsetSetting) {
|
|
int32_t minutes;
|
|
if (KiParseInt32(value, &minutes) && minutes >= -14 * 60 && minutes <= 14 * 60) {
|
|
policy.StandardOffsetMinutes = (int16_t)minutes;
|
|
policy.TimeZoneKnown = true;
|
|
fixedTimezone = true;
|
|
}
|
|
}
|
|
|
|
st = KeGetSettingString("time", "daylight_saving", value, sizeof(value));
|
|
if (KSUCCESS(st)) {
|
|
if (KiStringEqualsNoCase(value, "auto")) {
|
|
if (!fixedTimezone)
|
|
policy.DstRule = KeDstRuleCentralEurope;
|
|
} else if (KiStringEqualsNoCase(value, "central-europe")) {
|
|
policy.DstRule = KeDstRuleCentralEurope;
|
|
} else if (KiStringEqualsNoCase(value, "on") || KiStringEqualsNoCase(value, "yes") ||
|
|
// Implements the string Equals No Case operation.
|
|
KiStringEqualsNoCase(value, "true") || KiStringEqualsNoCase(value, "1")) {
|
|
policy.DstRule = KeDstRuleAlways;
|
|
} else if (KiStringEqualsNoCase(value, "off") || KiStringEqualsNoCase(value, "no") ||
|
|
// Implements the string Equals No Case operation.
|
|
KiStringEqualsNoCase(value, "false") || KiStringEqualsNoCase(value, "0")) {
|
|
policy.DstRule = KeDstRuleNone;
|
|
}
|
|
}
|
|
|
|
KE_HARDWARE_CLOCK_MODE hardwareClockMode = gHardwareClockMode;
|
|
st = KeGetSettingString("time", "hardware_clock", value, sizeof(value));
|
|
if (KSUCCESS(st)) {
|
|
if (KiStringEqualsNoCase(value, "utc") || KiStringEqualsNoCase(value, "z")) {
|
|
hardwareClockMode = KeHardwareClockUtc;
|
|
} else if (KiStringEqualsNoCase(value, "local") ||
|
|
KiStringEqualsNoCase(value, "localtime") ||
|
|
// Implements the string Equals No Case operation.
|
|
KiStringEqualsNoCase(value, "wall-clock")) {
|
|
hardwareClockMode = KeHardwareClockLocal;
|
|
}
|
|
}
|
|
|
|
gTimeZonePolicy = policy;
|
|
gHardwareClockMode = hardwareClockMode;
|
|
KiRebaseUtcFromSource();
|
|
|
|
SYS_SYSTEM_TIME now;
|
|
if (KeQuerySystemTime(&now) == NTLSTATUS_SUCCESS) {
|
|
DbgPrint("time: settings timezone=%d dst=%s hw=%s now=%04u-%02u-%02u %02u:%02u:%02u flags=0x%x epoch=%llu\n",
|
|
now.TimeZoneMinutes,
|
|
(now.Flags & SYS_TIME_FLAG_DAYLIGHT) ? "on" : "off",
|
|
gHardwareClockMode == KeHardwareClockLocal ? "local" : "utc",
|
|
now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second,
|
|
now.Flags, now.EpochSeconds);
|
|
}
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Returns system Time.
|
|
__declspec(dllexport) KSTATUS KeQuerySystemTime(SYS_SYSTEM_TIME* outTime) {
|
|
if (!outTime) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
uint64_t nowTick = (gTimeHal && gTimeHal->Timer.QueryTicks)
|
|
? gTimeHal->Timer.QueryTicks()
|
|
: gBaseTickMicroseconds;
|
|
uint64_t elapsedUs = nowTick - gBaseTickMicroseconds;
|
|
|
|
uint64_t utcEpochSeconds = gBaseUtcEpochSeconds + (elapsedUs / 1000000u);
|
|
uint64_t ns = (uint64_t)gSourceNanosecond + (elapsedUs % 1000000u) * 1000u;
|
|
if (ns >= 1000000000u) {
|
|
utcEpochSeconds += ns / 1000000000u;
|
|
ns %= 1000000000u;
|
|
}
|
|
|
|
bool daylight = false;
|
|
int16_t offset = KiOffsetForUtcEpoch(utcEpochSeconds, &daylight);
|
|
uint64_t offsetSeconds = (uint64_t)((offset < 0) ? -offset : offset) * 60u;
|
|
uint64_t localEpochSeconds;
|
|
if (offset >= 0) {
|
|
localEpochSeconds = utcEpochSeconds + offsetSeconds;
|
|
} else {
|
|
localEpochSeconds = (utcEpochSeconds > offsetSeconds) ? (utcEpochSeconds - offsetSeconds) : 0;
|
|
}
|
|
|
|
SYS_SYSTEM_TIME t;
|
|
memset(&t, 0, sizeof(t));
|
|
KiEpochSecondsToCalendar(localEpochSeconds, &t);
|
|
t.Nanosecond = (uint32_t)ns;
|
|
t.Millisecond = (uint16_t)(ns / 1000000u);
|
|
t.Flags = gSourceFlags;
|
|
if (gTimeZonePolicy.TimeZoneKnown) t.Flags |= SYS_TIME_FLAG_TZ_KNOWN;
|
|
if (daylight) t.Flags |= SYS_TIME_FLAG_DAYLIGHT;
|
|
t.TimeZoneMinutes = gTimeZonePolicy.TimeZoneKnown ? offset : KE_TZ_UNSPECIFIED;
|
|
t.EpochSeconds = utcEpochSeconds;
|
|
t.TickMicroseconds = nowTick;
|
|
|
|
*outTime = t;
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Returns the monotonic platform counter in microseconds.
|
|
uint64_t KeQueryMonotonicTicks(void) {
|
|
return (gTimeHal && gTimeHal->Timer.QueryTicks)
|
|
? gTimeHal->Timer.QueryTicks()
|
|
: 0;
|
|
}
|
|
|
|
// Returns a saturated monotonic deadline for a relative millisecond interval.
|
|
uint64_t KeMonotonicDeadlineAfterMilliseconds(uint64_t milliseconds) {
|
|
uint64_t now = KeQueryMonotonicTicks();
|
|
if (milliseconds > (UINT64_MAX - now) / 1000ull)
|
|
return UINT64_MAX;
|
|
return now + (milliseconds * 1000ull);
|
|
}
|
|
|
|
// Returns the remaining whole milliseconds, rounded up, until a monotonic deadline.
|
|
uint64_t KeMonotonicMillisecondsUntil(uint64_t deadlineMicroseconds) {
|
|
uint64_t now = KeQueryMonotonicTicks();
|
|
if (deadlineMicroseconds <= now)
|
|
return 0;
|
|
|
|
uint64_t remaining = deadlineMicroseconds - now;
|
|
uint64_t milliseconds = remaining / 1000ull;
|
|
if ((remaining % 1000ull) != 0)
|
|
milliseconds++;
|
|
return milliseconds;
|
|
}
|
|
|
|
// Returns unix Time Seconds.
|
|
__declspec(dllexport) uint64_t KeQueryUnixTimeSeconds(void) {
|
|
SYS_SYSTEM_TIME t;
|
|
if (KeQuerySystemTime(&t) != NTLSTATUS_SUCCESS)
|
|
return 0;
|
|
return t.EpochSeconds;
|
|
}
|