X-Git-Url: https://git.proxmox.com/?p=mirror_edk2.git;a=blobdiff_plain;f=ArmPkg%2FDrivers%2FTimerDxe%2FTimerDxe.c;h=a3202fa056f3026cd1c6ab3781649923d848d62c;hp=cbc34e8e412123515087f7e3e0080fbb93d84b44;hb=ac9b530e6b47c0957345e421b618d8bdd2bf21cf;hpb=1e57a46299244793beb27e74be171d1540606999 diff --git a/ArmPkg/Drivers/TimerDxe/TimerDxe.c b/ArmPkg/Drivers/TimerDxe/TimerDxe.c index cbc34e8e41..a3202fa056 100644 --- a/ArmPkg/Drivers/TimerDxe/TimerDxe.c +++ b/ArmPkg/Drivers/TimerDxe/TimerDxe.c @@ -1,15 +1,15 @@ /** @file Timer Architecture Protocol driver of the ARM flavor - Copyright (c) 2011 ARM Ltd. All rights reserved.
- - This program and the accompanying materials - are licensed and made available under the terms and conditions of the BSD License - which accompanies this distribution. The full text of the license may be found at - http://opensource.org/licenses/bsd-license.php + Copyright (c) 2011-2013 ARM Ltd. All rights reserved.
- THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, - WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. + This program and the accompanying materials + are licensed and made available under the terms and conditions of the BSD License + which accompanies this distribution. The full text of the license may be found at + http://opensource.org/licenses/bsd-license.php + + THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, + WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. **/ @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include #include @@ -35,21 +35,25 @@ EFI_EVENT EfiExitBootServicesEvent = (EFI_EVENT)NULL; // The current period of the timer interrupt UINT64 mTimerPeriod = 0; +// The latest Timer Tick calculated for mTimerPeriod +UINT64 mTimerTicks = 0; +// Number of elapsed period since the last Timer interrupt +UINT64 mElapsedPeriod = 1; // Cached copy of the Hardware Interrupt protocol instance EFI_HARDWARE_INTERRUPT_PROTOCOL *gInterrupt = NULL; /** - This function registers the handler NotifyFunction so it is called every time - the timer interrupt fires. It also passes the amount of time since the last - handler call to the NotifyFunction. If NotifyFunction is NULL, then the - handler is unregistered. If the handler is registered, then EFI_SUCCESS is - returned. If the CPU does not support registering a timer interrupt handler, - then EFI_UNSUPPORTED is returned. If an attempt is made to register a handler - when a handler is already registered, then EFI_ALREADY_STARTED is returned. - If an attempt is made to unregister a handler when a handler is not registered, - then EFI_INVALID_PARAMETER is returned. If an error occurs attempting to - register the NotifyFunction with the timer interrupt, then EFI_DEVICE_ERROR + This function registers the handler NotifyFunction so it is called every time + the timer interrupt fires. It also passes the amount of time since the last + handler call to the NotifyFunction. If NotifyFunction is NULL, then the + handler is unregistered. If the handler is registered, then EFI_SUCCESS is + returned. If the CPU does not support registering a timer interrupt handler, + then EFI_UNSUPPORTED is returned. If an attempt is made to register a handler + when a handler is already registered, then EFI_ALREADY_STARTED is returned. + If an attempt is made to unregister a handler when a handler is not registered, + then EFI_INVALID_PARAMETER is returned. If an error occurs attempting to + register the NotifyFunction with the timer interrupt, then EFI_DEVICE_ERROR is returned. @param This The EFI_TIMER_ARCH_PROTOCOL instance. @@ -97,22 +101,22 @@ ExitBootServicesEvent ( IN VOID *Context ) { - ArmArchTimerDisableTimer (); + ArmGenericTimerDisableTimer (); } /** - This function adjusts the period of timer interrupts to the value specified - by TimerPeriod. If the timer period is updated, then the selected timer - period is stored in EFI_TIMER.TimerPeriod, and EFI_SUCCESS is returned. If - the timer hardware is not programmable, then EFI_UNSUPPORTED is returned. - If an error occurs while attempting to update the timer period, then the - timer hardware will be put back in its state prior to this call, and - EFI_DEVICE_ERROR is returned. If TimerPeriod is 0, then the timer interrupt - is disabled. This is not the same as disabling the CPU's interrupts. - Instead, it must either turn off the timer hardware, or it must adjust the - interrupt controller so that a CPU interrupt is not generated when the timer - interrupt fires. + This function adjusts the period of timer interrupts to the value specified + by TimerPeriod. If the timer period is updated, then the selected timer + period is stored in EFI_TIMER.TimerPeriod, and EFI_SUCCESS is returned. If + the timer hardware is not programmable, then EFI_UNSUPPORTED is returned. + If an error occurs while attempting to update the timer period, then the + timer hardware will be put back in its state prior to this call, and + EFI_DEVICE_ERROR is returned. If TimerPeriod is 0, then the timer interrupt + is disabled. This is not the same as disabling the CPU's interrupts. + Instead, it must either turn off the timer hardware, or it must adjust the + interrupt controller so that a CPU interrupt is not generated when the timer + interrupt fires. @param This The EFI_TIMER_ARCH_PROTOCOL instance. @param TimerPeriod The rate to program the timer interrupt in 100 nS units. If @@ -135,32 +139,51 @@ TimerDriverSetTimerPeriod ( IN UINT64 TimerPeriod ) { + UINT64 CounterValue; UINT64 TimerTicks; - + EFI_TPL OriginalTPL; + // Always disable the timer - ArmArchTimerDisableTimer (); + ArmGenericTimerDisableTimer (); if (TimerPeriod != 0) { - // Convert TimerPeriod to micro sec units - TimerTicks = DivU64x32 (TimerPeriod, 10); + // mTimerTicks = TimerPeriod in 1ms unit x Frequency.10^-3 + // = TimerPeriod.10^-4 x Frequency.10^-3 + // = (TimerPeriod x Frequency) x 10^-7 + TimerTicks = MultU64x32 (TimerPeriod, ArmGenericTimerGetTimerFreq ()); + TimerTicks = DivU64x32 (TimerTicks, 10000000U); + + // Raise TPL to update the mTimerTicks and mTimerPeriod to ensure these values + // are coherent in the interrupt handler + OriginalTPL = gBS->RaiseTPL (TPL_HIGH_LEVEL); + + mTimerTicks = TimerTicks; + mTimerPeriod = TimerPeriod; + mElapsedPeriod = 1; - TimerTicks = MultU64x32 (TimerTicks, (PcdGet32(PcdArmArchTimerFreqInHz)/1000000)); + gBS->RestoreTPL (OriginalTPL); - ArmArchTimerSetTimerVal((UINTN)TimerTicks); + // Get value of the current timer + CounterValue = ArmGenericTimerGetSystemCount (); + // Set the interrupt in Current Time + mTimerTick + ArmGenericTimerSetCompareVal (CounterValue + mTimerTicks); // Enable the timer - ArmArchTimerEnableTimer (); + ArmGenericTimerEnableTimer (); + } else { + // Save the new timer period + mTimerPeriod = TimerPeriod; + // Reset the elapsed period + mElapsedPeriod = 1; } - // Save the new timer period - mTimerPeriod = TimerPeriod; return EFI_SUCCESS; } /** - This function retrieves the period of timer interrupts in 100 ns units, - returns that value in TimerPeriod, and returns EFI_SUCCESS. If TimerPeriod - is NULL, then EFI_INVALID_PARAMETER is returned. If a TimerPeriod of 0 is + This function retrieves the period of timer interrupts in 100 ns units, + returns that value in TimerPeriod, and returns EFI_SUCCESS. If TimerPeriod + is NULL, then EFI_INVALID_PARAMETER is returned. If a TimerPeriod of 0 is returned, then the timer is currently disabled. @param This The EFI_TIMER_ARCH_PROTOCOL instance. @@ -188,12 +211,12 @@ TimerDriverGetTimerPeriod ( } /** - This function generates a soft timer interrupt. If the platform does not support soft - timer interrupts, then EFI_UNSUPPORTED is returned. Otherwise, EFI_SUCCESS is returned. - If a handler has been registered through the EFI_TIMER_ARCH_PROTOCOL.RegisterHandler() - service, then a soft timer interrupt will be generated. If the timer interrupt is - enabled when this service is called, then the registered handler will be invoked. The - registered handler should not be able to distinguish a hardware-generated timer + This function generates a soft timer interrupt. If the platform does not support soft + timer interrupts, then EFI_UNSUPPORTED is returned. Otherwise, EFI_SUCCESS is returned. + If a handler has been registered through the EFI_TIMER_ARCH_PROTOCOL.RegisterHandler() + service, then a soft timer interrupt will be generated. If the timer interrupt is + enabled when this service is called, then the registered handler will be invoked. The + registered handler should not be able to distinguish a hardware-generated timer interrupt from a software-generated timer interrupt. @param This The EFI_TIMER_ARCH_PROTOCOL instance. @@ -273,6 +296,8 @@ TimerInterruptHandler ( ) { EFI_TPL OriginalTPL; + UINT64 CurrentValue; + UINT64 CompareValue; // // DXE core uses this callback for the EFI timer tick. The DXE core uses locks @@ -281,23 +306,41 @@ TimerInterruptHandler ( // OriginalTPL = gBS->RaiseTPL (TPL_HIGH_LEVEL); - // Check if the timer interrupt is active - if ((ArmArchTimerGetTimerCtrlReg () ) & ARM_ARCH_TIMER_ISTATUS) { + // Signal end of interrupt early to help avoid losing subsequent ticks + // from long duration handlers + gInterrupt->EndOfInterrupt (gInterrupt, Source); - // Signal end of interrupt early to help avoid losing subsequent ticks from long duration handlers - gInterrupt->EndOfInterrupt (gInterrupt, Source); + // Check if the timer interrupt is active + if ((ArmGenericTimerGetTimerCtrlReg () ) & ARM_ARCH_TIMER_ISTATUS) { if (mTimerNotifyFunction) { - mTimerNotifyFunction (mTimerPeriod); + mTimerNotifyFunction (mTimerPeriod * mElapsedPeriod); } + // // Reload the Timer - TimerDriverSetTimerPeriod (&gTimer, FixedPcdGet32(PcdTimerPeriod)); + // + + // Get current counter value + CurrentValue = ArmGenericTimerGetSystemCount (); + // Get the counter value to compare with + CompareValue = ArmGenericTimerGetCompareVal (); + + // This loop is needed in case we missed interrupts (eg: case when the interrupt handling + // has taken longer than mTickPeriod). + // Note: Physical Counter is counting up + mElapsedPeriod = 0; + do { + CompareValue += mTimerTicks; + mElapsedPeriod++; + } while (CompareValue < CurrentValue); + + // Set next compare value + ArmGenericTimerSetCompareVal (CompareValue); + ArmGenericTimerEnableTimer (); + ArmInstructionSynchronizationBarrier (); } - // Enable timer interrupts - gInterrupt->EnableInterruptSource (gInterrupt, Source); - gBS->RestoreTPL (OriginalTPL); } @@ -324,7 +367,8 @@ TimerInitialize ( { EFI_HANDLE Handle = NULL; EFI_STATUS Status; - UINTN TimerCtrlReg; + UINTN TimerCtrlReg; + UINT32 TimerHypIntrNum; if (ArmIsArchTimerImplemented () == 0) { DEBUG ((EFI_D_ERROR, "ARM Architectural Timer is not available in the CPU, hence cann't use this Driver \n")); @@ -336,6 +380,10 @@ TimerInitialize ( ASSERT_EFI_ERROR (Status); // Disable the timer + TimerCtrlReg = ArmGenericTimerGetTimerCtrlReg (); + TimerCtrlReg |= ARM_ARCH_TIMER_IMASK; + TimerCtrlReg &= ~ARM_ARCH_TIMER_ENABLE; + ArmGenericTimerSetTimerCtrlReg (TimerCtrlReg); Status = TimerDriverSetTimerPeriod (&gTimer, 0); ASSERT_EFI_ERROR (Status); @@ -343,17 +391,25 @@ TimerInitialize ( // Note: Because it is not possible to determine the security state of the // CPU dynamically, we just install interrupt handler for both sec and non-sec // timer PPI + Status = gInterrupt->RegisterInterruptSource (gInterrupt, PcdGet32 (PcdArmArchTimerVirtIntrNum), TimerInterruptHandler); + ASSERT_EFI_ERROR (Status); + + // + // The hypervisor timer interrupt may be omitted by implementations that + // execute under virtualization. + // + TimerHypIntrNum = PcdGet32 (PcdArmArchTimerHypIntrNum); + if (TimerHypIntrNum != 0) { + Status = gInterrupt->RegisterInterruptSource (gInterrupt, TimerHypIntrNum, TimerInterruptHandler); + ASSERT_EFI_ERROR (Status); + } + Status = gInterrupt->RegisterInterruptSource (gInterrupt, PcdGet32 (PcdArmArchTimerSecIntrNum), TimerInterruptHandler); ASSERT_EFI_ERROR (Status); Status = gInterrupt->RegisterInterruptSource (gInterrupt, PcdGet32 (PcdArmArchTimerIntrNum), TimerInterruptHandler); ASSERT_EFI_ERROR (Status); - // Unmask timer interrupts - TimerCtrlReg = ArmArchTimerGetTimerCtrlReg (); - TimerCtrlReg &= ~ARM_ARCH_TIMER_IMASK; - ArmArchTimerSetTimerCtrlReg (TimerCtrlReg); - // Set up default timer Status = TimerDriverSetTimerPeriod (&gTimer, FixedPcdGet32(PcdTimerPeriod)); // TIMER_DEFAULT_PERIOD ASSERT_EFI_ERROR (Status); @@ -366,11 +422,9 @@ TimerInitialize ( ); ASSERT_EFI_ERROR(Status); - // enable Secure timer interrupts - Status = gInterrupt->EnableInterruptSource (gInterrupt, PcdGet32 (PcdArmArchTimerSecIntrNum)); - - // enable NonSecure timer interrupts - Status = gInterrupt->EnableInterruptSource (gInterrupt, PcdGet32 (PcdArmArchTimerIntrNum)); + // Everything is ready, unmask and enable timer interrupts + TimerCtrlReg = ARM_ARCH_TIMER_ENABLE; + ArmGenericTimerSetTimerCtrlReg (TimerCtrlReg); // Register for an ExitBootServicesEvent Status = gBS->CreateEvent (EVT_SIGNAL_EXIT_BOOT_SERVICES, TPL_NOTIFY, ExitBootServicesEvent, NULL, &EfiExitBootServicesEvent);