From 907d376eee5d6cb66252ff261f0d6f627398008e Mon Sep 17 00:00:00 2001 From: Joseph Lee Date: Tue, 14 Nov 2023 22:04:20 +0900 Subject: [PATCH] feat: time.now --- src/device/x86/cpu.go | 132 ++++++++++++++++----------------- src/machine/uefi/clock.go | 59 +++++++++++++++ src/machine/uefi/efiapi.go | 70 ++++++++++-------- src/machine/uefi/util.go | 15 ++-- src/runtime/runtime_uefi.go | 144 +++++++++++++++++++++++++++++++++++- 5 files changed, 309 insertions(+), 111 deletions(-) create mode 100644 src/machine/uefi/clock.go diff --git a/src/device/x86/cpu.go b/src/device/x86/cpu.go index 3aa9d37155..a96d972230 100644 --- a/src/device/x86/cpu.go +++ b/src/device/x86/cpu.go @@ -1,3 +1,5 @@ +//go:build amd64 + package x86 const ( @@ -26,15 +28,69 @@ const ( func AsmReadRdtsc() uint64 //export asmCpuid -func AsmCpuid(index int, registerEax *uint32, registerRbx *uint32, registerEcx *uint32, registerEdx *uint32) +func AsmCpuid(index int, registerEax *uint32, registerRbx *uint32, registerEcx *uint32, registerEdx *uint32) int + +var maxCpuidIndex int + +func init() { + maxCpuidIndex = AsmCpuid(0, nil, nil, nil, nil) +} + +func GetMaxCpuidIndex() int { + return maxCpuidIndex +} func InternalGetPerformanceCounterFrequency() uint64 { - return CpuidCoreClockCalculateTscFrequency() + //FIXME: how to find it? + //See https://github.com/torvalds/linux/blob/master/arch/x86/kernel/tsc.c + + if maxCpuidIndex >= CPUID_TIME_STAMP_COUNTER { + return CpuidCoreClockCalculateTscFrequency() + } + + return 0 } -func GetTimeInNanoSecond(ticks uint64) int64 { - frequency := InternalGetPerformanceCounterFrequency() - return ConvertTimeInNanoSecond(frequency, ticks) +//func GetTimeInNanoSecond(ticks uint64) int64 { +// frequency := InternalGetPerformanceCounterFrequency() +// return ConvertTimeInNanoSecond(frequency, ticks) +//} +// +//func GetTicksFromNanoSeconds(nano uint64) uint64 { +// frequency := InternalGetPerformanceCounterFrequency() +// return nano * frequency / 1000000000 +//} + +func CpuidCoreClockCalculateTscFrequency() uint64 { + var TscFrequency uint64 + var CoreXtalFrequency uint64 + var RegEax uint32 + var RegEbx uint32 + var RegEcx uint32 + + AsmCpuid(CPUID_TIME_STAMP_COUNTER, &RegEax, &RegEbx, &RegEcx, nil) + + // If EAX or EBX returns 0, the XTAL ratio is not enumerated. + if (RegEax == 0) || (RegEbx == 0) { + return 0 + } + + // If ECX returns 0, the XTAL frequency is not enumerated. + // And PcdCpuCoreCrystalClockFrequency defined should base on processor series. + // + if RegEcx == 0 { + // Specifies CPUID Leaf 0x15 Time Stamp Counter and Nominal Core Crystal Clock Frequency. + // Intel Xeon Processor Scalable Family with CPUID signature 06_55H = 25000000 (25MHz) + // 6th and 7th generation Intel Core processors and Intel Xeon W Processor Family = 24000000 (24MHz) + // Intel Atom processors based on Goldmont Microarchitecture with CPUID signature 06_5CH = 19200000 (19.2MHz) + CoreXtalFrequency = 24000000 + } else { + CoreXtalFrequency = uint64(RegEcx) + } + + // Calculate TSC frequency = (ECX, Core Xtal Frequency) * EBX/EAX + TscFrequency = ((CoreXtalFrequency * uint64(RegEbx)) + (uint64(RegEax) / 2)) / uint64(RegEax) + return TscFrequency } func ConvertTimeInNanoSecond(frequency uint64, ticks uint64) int64 { @@ -59,8 +115,7 @@ func ConvertTimeInNanoSecond(frequency uint64, ticks uint64) int64 { return int64(nanoSeconds) } -func GetTicksFromNanoSeconds(nano uint64) uint64 { - frequency := InternalGetPerformanceCounterFrequency() +func ConvertTicksFromNanoSeconds(frequency uint64, nano uint64) uint64 { return nano * frequency / 1000000000 } @@ -82,66 +137,3 @@ func highBitSet32(operand uint32) int { } return bitIndex } - -/* - -UINT64 -EFIAPI -GetTimeInNanoSecond ( - IN UINT64 Ticks - ) -{ - UINT64 Frequency; - UINT64 NanoSeconds; - UINT64 Remainder; - INTN Shift; - - Frequency = GetPerformanceCounterProperties (NULL, NULL); - - NanoSeconds = MultU64x32 (DivU64x64Remainder (Ticks, Frequency, &Remainder), 1000000000u); - - // - // Ensure (Remainder * 1,000,000,000) will not overflow 64-bit. - // Since 2^29 < 1,000,000,000 = 0x3B9ACA00 < 2^30, Remainder should < 2^(64-30) = 2^34, - // i.e. highest bit set in Remainder should <= 33. - // - Shift = MAX (0, HighBitSet64 (Remainder) - 33); - Remainder = RShiftU64 (Remainder, (UINTN)Shift); - Frequency = RShiftU64 (Frequency, (UINTN)Shift); - NanoSeconds += DivU64x64Remainder (MultU64x32 (Remainder, 1000000000u), Frequency, NULL); - - return NanoSeconds; -} -*/ - -func CpuidCoreClockCalculateTscFrequency() uint64 { - var TscFrequency uint64 - var CoreXtalFrequency uint64 - var RegEax uint32 - var RegEbx uint32 - var RegEcx uint32 - - AsmCpuid(CPUID_TIME_STAMP_COUNTER, &RegEax, &RegEbx, &RegEcx, nil) - - // If EAX or EBX returns 0, the XTAL ratio is not enumerated. - if (RegEax == 0) || (RegEbx == 0) { - return 0 - } - - // If ECX returns 0, the XTAL frequency is not enumerated. - // And PcdCpuCoreCrystalClockFrequency defined should base on processor series. - // - if RegEcx == 0 { - // Specifies CPUID Leaf 0x15 Time Stamp Counter and Nominal Core Crystal Clock Frequency. - // Intel Xeon Processor Scalable Family with CPUID signature 06_55H = 25000000 (25MHz) - // 6th and 7th generation Intel Core processors and Intel Xeon W Processor Family = 24000000 (24MHz) - // Intel Atom processors based on Goldmont Microarchitecture with CPUID signature 06_5CH = 19200000 (19.2MHz) - CoreXtalFrequency = 24000000 - } else { - CoreXtalFrequency = uint64(RegEcx) - } - - // Calculate TSC frequency = (ECX, Core Xtal Frequency) * EBX/EAX - TscFrequency = ((CoreXtalFrequency * uint64(RegEbx)) + (uint64(RegEax) / 2)) / uint64(RegEax) - return TscFrequency -} diff --git a/src/machine/uefi/clock.go b/src/machine/uefi/clock.go new file mode 100644 index 0000000000..4fa81a9b19 --- /dev/null +++ b/src/machine/uefi/clock.go @@ -0,0 +1,59 @@ +//go:build uefi + +package uefi + +import ( + "device/x86" + "sync" +) + +var calibrateMutex sync.Mutex +var calculatedFrequency uint64 + +func GetTscFrequency() uint64 { + frequence := x86.InternalGetPerformanceCounterFrequency() + if frequence > 0 { + return frequence + } + + var event EFI_EVENT + var status EFI_STATUS + var index UINTN + + calibrateMutex.Lock() + defer calibrateMutex.Unlock() + + freq := calculatedFrequency + if freq > 0 { + return freq + } + + bs := systemTable.BootServices + + status = bs.CreateEvent(EVT_TIMER, TPL_CALLBACK, nil, nil, &event) + if status != EFI_SUCCESS { + return 0 + } + defer bs.CloseEvent(event) + + st := x86.AsmReadRdtsc() + status = bs.SetTimer(event, TimerPeriodic, 250*10000) + if status != EFI_SUCCESS { + return 0 + } + status = bs.WaitForEvent(1, &event, &index) + diff := x86.AsmReadRdtsc() - st + + calculatedFrequency = diff * 4 + + return calculatedFrequency +} + +func GetTime() (EFI_TIME, EFI_STATUS) { + var status EFI_STATUS + var time EFI_TIME + + status = systemTable.RuntimeServices.GetTime(&time, nil) + + return time, status +} diff --git a/src/machine/uefi/efiapi.go b/src/machine/uefi/efiapi.go index bae514f3c5..2eb9ca4bbb 100644 --- a/src/machine/uefi/efiapi.go +++ b/src/machine/uefi/efiapi.go @@ -88,19 +88,21 @@ type EFI_CONVERT_POINTER func(DebugDisposition UINTN, Address *uintptr) EFI_STAT //region: EFI Events +type EVENT_TYPE uint32 + const ( - EVT_TIMER = 0x80000000 - EVT_RUNTIME = 0x40000000 - EVT_RUNTIME_CONTEXT = 0x20000000 + EVT_TIMER EVENT_TYPE = 0x80000000 + EVT_RUNTIME EVENT_TYPE = 0x40000000 + EVT_RUNTIME_CONTEXT EVENT_TYPE = 0x20000000 - EVT_NOTIFY_WAIT = 0x00000100 - EVT_NOTIFY_SIGNAL = 0x00000200 + EVT_NOTIFY_WAIT EVENT_TYPE = 0x00000100 + EVT_NOTIFY_SIGNAL EVENT_TYPE = 0x00000200 - EVT_SIGNAL_EXIT_BOOT_SERVICES = 0x00000201 - EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE = 0x60000202 + EVT_SIGNAL_EXIT_BOOT_SERVICES EVENT_TYPE = 0x00000201 + EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE EVENT_TYPE = 0x60000202 - EVT_EFI_SIGNAL_MASK = 0x000000FF - EVT_EFI_SIGNAL_MAX = 4 + EVT_EFI_SIGNAL_MASK EVENT_TYPE = 0x000000FF + EVT_EFI_SIGNAL_MAX EVENT_TYPE = 4 EFI_EVENT_TIMER = EVT_TIMER EFI_EVENT_RUNTIME = EVT_RUNTIME @@ -171,14 +173,14 @@ type EFI_CHECK_EVENT func(Event EFI_EVENT) EFI_STATUS //region: Task priority level const ( - TPL_APPLICATION = 4 - TPL_CALLBACK = 8 - TPL_NOTIFY = 16 - TPL_HIGH_LEVEL = 31 - EFI_TPL_APPLICATION = TPL_APPLICATION - EFI_TPL_CALLBACK = TPL_CALLBACK - EFI_TPL_NOTIFY = TPL_NOTIFY - EFI_TPL_HIGH_LEVEL = TPL_HIGH_LEVEL + TPL_APPLICATION EFI_TPL = 4 + TPL_CALLBACK EFI_TPL = 8 + TPL_NOTIFY EFI_TPL = 16 + TPL_HIGH_LEVEL EFI_TPL = 31 + EFI_TPL_APPLICATION = TPL_APPLICATION + EFI_TPL_CALLBACK = TPL_CALLBACK + EFI_TPL_NOTIFY = TPL_NOTIFY + EFI_TPL_HIGH_LEVEL = TPL_HIGH_LEVEL ) // EFI_RAISE_TPL @@ -633,35 +635,39 @@ type EFI_RUNTIME_SERVICES struct { // // Time services // - GetTime *EFI_GET_TIME - SetTime *EFI_SET_TIME - GetWakeupTime *EFI_GET_WAKEUP_TIME - SetWakeupTime *EFI_SET_WAKEUP_TIME + getTime uintptr + setTime uintptr + getWakeupTime uintptr + setWakeupTime uintptr // // Virtual memory services // - SetVirtualAddressMap *EFI_SET_VIRTUAL_ADDRESS_MAP - ConvertPointer *EFI_CONVERT_POINTER + setVirtualAddressMap *EFI_SET_VIRTUAL_ADDRESS_MAP + convertPointer *EFI_CONVERT_POINTER // // Variable serviers // - GetVariable *EFI_GET_VARIABLE - GetNextVariableName *EFI_GET_NEXT_VARIABLE_NAME - SetVariable *EFI_SET_VARIABLE + getVariable uintptr + getNextVariableName uintptr + setVariable uintptr // // Misc // - GetNextHighMonotonicCount *EFI_GET_NEXT_HIGH_MONO_COUNT - ResetSystem *EFI_RESET_SYSTEM + getNextHighMonotonicCount uintptr + resetSystem uintptr + + updateCapsule uintptr + queryCapsuleCapabilities uintptr + queryVariableInfo uintptr +} - UpdateCapsule *EFI_UPDATE_CAPSULE - QueryCapsuleCapabilities *EFI_QUERY_CAPSULE_CAPABILITIES - QueryVariableInfo *EFI_QUERY_VARIABLE_INFO +func (s *EFI_RUNTIME_SERVICES) GetTime(Time *EFI_TIME, Capabilities *EFI_TIME_CAPABILITIES) EFI_STATUS { + return UefiCall2(s.getTime, uintptr(unsafe.Pointer(Time)), uintptr(unsafe.Pointer(Capabilities))) } //endregion @@ -772,7 +778,7 @@ func (s *EFI_BOOT_SERVICES) FreePool(Buffer uintptr) EFI_STATUS { // @param NotifyFunction IN // @param NotifyContext IN // @param Event OUT -func (s *EFI_BOOT_SERVICES) CreateEvent(Type uint32, NotifyTpl EFI_TPL, NotifyFunction unsafe.Pointer, NotifyContext unsafe.Pointer, Event *EFI_EVENT) EFI_STATUS { +func (s *EFI_BOOT_SERVICES) CreateEvent(Type EVENT_TYPE, NotifyTpl EFI_TPL, NotifyFunction unsafe.Pointer, NotifyContext unsafe.Pointer, Event *EFI_EVENT) EFI_STATUS { return UefiCall5(s.createEvent, uintptr(Type), uintptr(NotifyTpl), uintptr(NotifyFunction), uintptr(NotifyContext), uintptr(unsafe.Pointer(Event))) } diff --git a/src/machine/uefi/util.go b/src/machine/uefi/util.go index 0bb7565b7d..04843ed5b5 100644 --- a/src/machine/uefi/util.go +++ b/src/machine/uefi/util.go @@ -1,3 +1,5 @@ +//go:build uefi + package uefi import ( @@ -29,6 +31,14 @@ func GetImageHandle() EFI_HANDLE { return EFI_HANDLE(imageHandle) } +func Ticks() uint64 { + return x86.AsmReadRdtsc() +} + +func TicksFrequency() uint64 { + return GetTscFrequency() +} + var hexConst [16]CHAR16 = [16]CHAR16{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'} var printBuf [256]CHAR16 @@ -55,8 +65,3 @@ func DebugPrint(text string, val uint64) { systemTable.ConOut.OutputString(systemTable.ConOut, (*CHAR16)(unsafe.Pointer(&printBuf[0]))) } - -//go:linkname ticks runtime.machineTicks -func ticks() uint64 { - return x86.AsmReadRdtsc() -} diff --git a/src/runtime/runtime_uefi.go b/src/runtime/runtime_uefi.go index d2c6a891fc..b30bb3952b 100644 --- a/src/runtime/runtime_uefi.go +++ b/src/runtime/runtime_uefi.go @@ -13,8 +13,9 @@ var systemTable *uefi.EFI_SYSTEM_TABLE //go:linkname imageHandle machine/uefi.imageHandle var imageHandle uintptr -// machineTicks is provided by package machine. -func machineTicks() uint64 +func machineTicks() uint64 { + return uefi.Ticks() +} type timeUnit uint64 @@ -25,11 +26,49 @@ func ticks() timeUnit { } func ticksToNanoseconds(ticks timeUnit) int64 { - return x86.GetTimeInNanoSecond(uint64(ticks)) + return x86.ConvertTimeInNanoSecond(uefi.TicksFrequency(), uint64(ticks)) } func nanosecondsToTicks(ns int64) timeUnit { - return timeUnit(ns / 1000) + return timeUnit(x86.ConvertTicksFromNanoSeconds(uefi.TicksFrequency(), uint64(ns))) +} + +// timeOffset +// TODO: use rtc +var timeOffset int64 + +//go:linkname now time.now +func now() (sec int64, nsec int32, mono int64) { + mono = nanotime() + efiTime, status := uefi.GetTime() + if status != uefi.EFI_SUCCESS { + uefi.DebugPrint("WHAT THE", uint64(status)) + sec = (mono + timeOffset) / (1000 * 1000 * 1000) + nsec = int32((mono + timeOffset) - sec*(1000*1000*1000)) + } else { + year := int(efiTime.Year) + month := int(efiTime.Month) - 1 + + // Compute days since the absolute epoch. + d := daysSinceEpoch(year) + + // Add in days before this month. + d += uint64(daysBefore[month-1]) + if isLeap(year) && month >= March { + d++ // February 29 + } + + // Add in days before today. + d += uint64(efiTime.Day - 1) + + // Add in time elapsed today. + abs := d * secondsPerDay + abs += uint64(uint64(efiTime.Hour)*uint64(secondsPerHour) + uint64(efiTime.Minute)*uint64(secondsPerMinute) + uint64(efiTime.Second)) + + sec = int64(abs) + (absoluteToInternal + internalToUnix) + nsec = int32(efiTime.Nanosecond) + } + return } func sleepTicks(d timeUnit) { @@ -135,3 +174,100 @@ func main(imageHandle uintptr, systemTable uintptr) uintptr { // For libc compatibility. return 0 } + +// region: time utils +const ( + secondsPerMinute = 60 + secondsPerHour = 60 * secondsPerMinute + secondsPerDay = 24 * secondsPerHour + secondsPerWeek = 7 * secondsPerDay + daysPer400Years = 365*400 + 97 + daysPer100Years = 365*100 + 24 + daysPer4Years = 365*4 + 1 + + // The unsigned zero year for internal calculations. + // Must be 1 mod 400, and times before it will not compute correctly, + // but otherwise can be changed at will. + absoluteZeroYear = -292277022399 + + // The year of the zero Time. + // Assumed by the unixToInternal computation below. + internalYear = 1 + + // Offsets to convert between internal and absolute or Unix times. + absoluteToInternal int64 = (absoluteZeroYear - internalYear) * 365.2425 * secondsPerDay + internalToAbsolute = -absoluteToInternal + + unixToInternal int64 = (1969*365 + 1969/4 - 1969/100 + 1969/400) * secondsPerDay + internalToUnix int64 = -unixToInternal + + wallToInternal int64 = (1884*365 + 1884/4 - 1884/100 + 1884/400) * secondsPerDay +) + +const ( + January = 1 + iota + February + March + April + May + June + July + August + September + October + November + December +) + +// daysBefore[m] counts the number of days in a non-leap year +// before month m begins. There is an entry for m=12, counting +// the number of days before January of next year (365). +var daysBefore = [...]int32{ + 0, + 31, + 31 + 28, + 31 + 28 + 31, + 31 + 28 + 31 + 30, + 31 + 28 + 31 + 30 + 31, + 31 + 28 + 31 + 30 + 31 + 30, + 31 + 28 + 31 + 30 + 31 + 30 + 31, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31, +} + +// daysSinceEpoch takes a year and returns the number of days from +// the absolute epoch to the start of that year. +// This is basically (year - zeroYear) * 365, but accounting for leap days. +func daysSinceEpoch(year int) uint64 { + y := uint64(int64(year) - absoluteZeroYear) + + // Add in days from 400-year cycles. + n := y / 400 + y -= 400 * n + d := daysPer400Years * n + + // Add in 100-year cycles. + n = y / 100 + y -= 100 * n + d += daysPer100Years * n + + // Add in 4-year cycles. + n = y / 4 + y -= 4 * n + d += daysPer4Years * n + + // Add in non-leap years. + n = y + d += 365 * n + + return d +} + +func isLeap(year int) bool { + return year%4 == 0 && (year%100 != 0 || year%400 == 0) +} + +//endregion