Old logic is simply to get the APIC Timer Frequency from PcdFSBClock. New logic follows the SDM to firstly get the crystal clock frequency from CPUID.0x15 leaf. If CPUID.0x15 is not supported, the crystal clock frequency is retrieved from PcdCpuCoreCrystalClockFrequency. Then the new logic finds a proper divisor that could support longer timer period. Usually divisor 1 is good enough. But when the timer period is very long, divisor 4, 8, or even 128 could be used.
Signed-off-by: Ray Ni <ray...@intel.com> Cc: Michael D Kinney <michael.d.kin...@intel.com> Cc: Nate DeSimone <nathaniel.l.desim...@intel.com> Cc: Laszlo Ersek <ler...@redhat.com> Cc: Rahul Kumar <rahul1.ku...@intel.com> Cc: Gerd Hoffmann <kra...@redhat.com> --- .../LocalApicTimerDxe/LocalApicTimerDxe.c | 120 +++++++++++++++--- .../LocalApicTimerDxe/LocalApicTimerDxe.h | 4 +- .../LocalApicTimerDxe/LocalApicTimerDxe.inf | 2 +- 3 files changed, 107 insertions(+), 19 deletions(-) diff --git a/UefiCpuPkg/LocalApicTimerDxe/LocalApicTimerDxe.c b/UefiCpuPkg/LocalApicTimerDxe/LocalApicTimerDxe.c index f36a0e6bf3..babf2476e3 100644 --- a/UefiCpuPkg/LocalApicTimerDxe/LocalApicTimerDxe.c +++ b/UefiCpuPkg/LocalApicTimerDxe/LocalApicTimerDxe.c @@ -128,6 +128,50 @@ TimerDriverRegisterHandler ( return EFI_SUCCESS; } +/** + Return the Crystal Clock Frequency in Hz. + + If CPUID.0x15 is supported, the Crystal Clock Frequency is retrieved from CPUID.0x15.ECX. + Otherwise, the Crystal Clock Frequency is retrieved from PcdCpuCoreCrystalClockFrequency. + + @return the Crystal Clock Frequency in Hz. +**/ +UINT64 +GetCoreXtalClockFrequency ( + VOID + ) +{ + UINT32 MaxLeaf; + UINT32 RegEax; + UINT32 RegEbx; + UINT32 CoreXtalClockFrequency; + + CoreXtalClockFrequency = 0; + AsmCpuid (CPUID_SIGNATURE, &MaxLeaf, NULL, NULL, NULL); + if (MaxLeaf >= CPUID_TIME_STAMP_COUNTER) { + // + // Use CPUID leaf 0x15 Time Stamp Counter and Nominal Core Crystal Clock Information + // EBX returns 0 if not supported. ECX, if non zero, provides Core Xtal Frequency in hertz. + // TSC frequency = (ECX, Core Xtal Frequency) * EBX/EAX. + // + AsmCpuid (CPUID_TIME_STAMP_COUNTER, &RegEax, &RegEbx, (UINT32 *)&CoreXtalClockFrequency, NULL); + + // + // If EAX or EBX returns 0, the Crystal ratio is not enumerated. + // + if ((RegEax == 0) || (RegEbx == 0)) { + CoreXtalClockFrequency = 0; + } + } + + if (CoreXtalClockFrequency != 0) { + return CoreXtalClockFrequency; + } else { + DEBUG ((DEBUG_INFO, "%a: Using PcdCpuCoreCrystalClockFrequency (%ld)\n", __func__, PcdGet64 (PcdCpuCoreCrystalClockFrequency))); + return PcdGet64 (PcdCpuCoreCrystalClockFrequency); + } +} + /** This function adjusts the period of timer interrupts to the value specified @@ -163,9 +207,13 @@ TimerDriverSetTimerPeriod ( IN UINT64 TimerPeriod ) { - UINT64 TimerCount; - UINT32 TimerFrequency; - UINT32 DivideValue = 1; + UINT64 InitialCount; + UINT64 CoreXtalClockFrequency; + UINT64 ApicTimerFrequency; + UINT8 Divisor; + UINT32 TimerPeriodDivisor; + + DEBUG ((DEBUG_INFO, "%a: TimerPeriod = %d (100ns)\n", __func__, TimerPeriod)); if (TimerPeriod == 0) { // @@ -173,29 +221,67 @@ TimerDriverSetTimerPeriod ( // DisableApicTimerInterrupt (); } else { - TimerFrequency = PcdGet32 (PcdFSBClock) / (UINT32)DivideValue; + CoreXtalClockFrequency = GetCoreXtalClockFrequency (); + // + // Find a good Divisor that can support the TimerPeriod. + // + for (Divisor = 1; Divisor <= 128; Divisor *= 2) { + ApicTimerFrequency = DivU64x32 (CoreXtalClockFrequency, Divisor); + + // + // TimerPeriod + // InitialCount = ---------------- * ApicTimerFrequency + // 10 * 1000 * 1000 + // + // So, + // + // InitialCount * 10 * 1000 * 1000 + // ApicTimerFrequency = ------------------------------ + // TimerPeriod + // + // Because InitialCount is a UINT32, the maximum ApicTimerFrequency is: + // + // MAX_UINT32 * 10 * 1000 * 1000 + // ------------------------------ + // TimerPeriod + // + if (ApicTimerFrequency <= DivU64x64Remainder (MultU64x32 (MAX_UINT32, 10 * 1000 * 1000), TimerPeriod, NULL)) { + break; + } + } + + DEBUG (( + DEBUG_INFO, + "%a: ApicTimerFrequency (%d) = CoreXtalClockFrequency (%d) / Divisor (%d)\n", + __func__, + ApicTimerFrequency, + CoreXtalClockFrequency, + Divisor + )); // - // Convert TimerPeriod into local APIC counts + // Convert TimerPeriod (in 100ns) into local APIC counts + // TimerPeriod + // InitialCount = ---------------- * ApicTimerFrequency + // 10 * 1000 * 1000 // - // TimerPeriod is in 100ns - // TimerPeriod/10000000 will be in seconds. - TimerCount = DivU64x32 ( - MultU64x32 (TimerPeriod, TimerFrequency), - 10000000 - ); + TimerPeriodDivisor = 10 * 1000 * 1000; - // Check for overflow - if (TimerCount > MAX_UINT32) { - TimerCount = MAX_UINT32; - /* TimerPeriod = (MAX_UINT32 / TimerFrequency) * 10000000; */ - TimerPeriod = 429496730; + // + // If TimerPeriod * ApicTimerFrequency > MAX_UINT64, divide TimerPeriod by 10 until the result <= MAX_UINT64. + // + while (TimerPeriod > DivU64x64Remainder (MAX_UINT64, ApicTimerFrequency, NULL)) { + TimerPeriod = DivU64x32 (TimerPeriod, 10); + TimerPeriodDivisor /= 10; } + InitialCount = DivU64x64Remainder (MultU64x64 (TimerPeriod, ApicTimerFrequency), TimerPeriodDivisor, NULL); + DEBUG ((DEBUG_INFO, "%a: InitialCount = %d\n", __func__, InitialCount)); + // // Program the timer with the new count value // - InitializeApicTimer (DivideValue, (UINT32)TimerCount, TRUE, LOCAL_APIC_TIMER_VECTOR); + InitializeApicTimer (Divisor, (UINT32)InitialCount, TRUE, LOCAL_APIC_TIMER_VECTOR); // // Enable timer interrupt diff --git a/UefiCpuPkg/LocalApicTimerDxe/LocalApicTimerDxe.h b/UefiCpuPkg/LocalApicTimerDxe/LocalApicTimerDxe.h index 93706995f8..d7f38a3dc3 100644 --- a/UefiCpuPkg/LocalApicTimerDxe/LocalApicTimerDxe.h +++ b/UefiCpuPkg/LocalApicTimerDxe/LocalApicTimerDxe.h @@ -1,7 +1,7 @@ /** @file Private data structures -Copyright (c) 2005 - 2018, Intel Corporation. All rights reserved.<BR> +Copyright (c) 2005 - 2024, Intel Corporation. All rights reserved.<BR> Copyright (c) 2019, Citrix Systems, Inc. SPDX-License-Identifier: BSD-2-Clause-Patent @@ -16,12 +16,14 @@ SPDX-License-Identifier: BSD-2-Clause-Patent #include <Protocol/Timer.h> #include <Register/LocalApic.h> +#include <Register/Cpuid.h> #include <Library/UefiBootServicesTableLib.h> #include <Library/BaseLib.h> #include <Library/DebugLib.h> #include <Library/LocalApicLib.h> #include <Library/PcdLib.h> +#include <Library/TimerLib.h> // The default timer tick duration is set to 10 ms = 100000 100 ns units // diff --git a/UefiCpuPkg/LocalApicTimerDxe/LocalApicTimerDxe.inf b/UefiCpuPkg/LocalApicTimerDxe/LocalApicTimerDxe.inf index 874d58fa17..a468f09566 100644 --- a/UefiCpuPkg/LocalApicTimerDxe/LocalApicTimerDxe.inf +++ b/UefiCpuPkg/LocalApicTimerDxe/LocalApicTimerDxe.inf @@ -37,6 +37,6 @@ gEfiCpuArchProtocolGuid ## CONSUMES gEfiTimerArchProtocolGuid ## PRODUCES [Pcd] - gEfiMdePkgTokenSpaceGuid.PcdFSBClock ## CONSUMES + gUefiCpuPkgTokenSpaceGuid.PcdCpuCoreCrystalClockFrequency ## CONSUMES [Depex] gEfiCpuArchProtocolGuid -- 2.39.1.windows.1 -=-=-=-=-=-=-=-=-=-=-=- Groups.io Links: You receive all messages sent to this group. View/Reply Online (#113803): https://edk2.groups.io/g/devel/message/113803 Mute This Topic: https://groups.io/mt/103734964/21656 Group Owner: devel+ow...@edk2.groups.io Unsubscribe: https://edk2.groups.io/g/devel/leave/9847357/21656/1706620634/xyzzy [arch...@mail-archive.com] -=-=-=-=-=-=-=-=-=-=-=-