511 lines
16 KiB
C
511 lines
16 KiB
C
/** @file
|
|
Timer Architectural PPI and Time0 notify callback
|
|
|
|
;******************************************************************************
|
|
;* Copyright (c) 2015 - 2020, Insyde Software Corporation. All Rights Reserved.
|
|
;*
|
|
;* You may not reproduce, distribute, publish, display, perform, modify, adapt,
|
|
;* transmit, broadcast, present, recite, release, license or otherwise exploit
|
|
;* any part of this publication in any form, by any means, without the prior
|
|
;* written permission of Insyde Software Corporation.
|
|
;*
|
|
;******************************************************************************
|
|
*/
|
|
#include <SmartTimer.h>
|
|
|
|
H2O_LEGACY_8259_PPI *m8259Ppi;
|
|
H2O_CPU_ARCH_PPI *mCpuPpi;
|
|
CONST EFI_PEI_SERVICES **mPeiServices;
|
|
|
|
//
|
|
// The current period of the timer interrupt
|
|
//
|
|
volatile UINT64 mTimerPeriod = 0;
|
|
|
|
//
|
|
// The time of twice timer interrupt duration
|
|
//
|
|
volatile UINTN mPreAcpiTick = 0;
|
|
|
|
//
|
|
// The notification function to call on every timer interrupt.
|
|
//
|
|
volatile H2O_TIMER_NOTIFY mTimerNotifyFunction;
|
|
|
|
//
|
|
// PMIO BAR Registers
|
|
//
|
|
UINT16 mPchPmioBase;
|
|
|
|
//
|
|
// The Timer Architectural PPI that this module produces
|
|
//
|
|
H2O_TIMER_ARCH_PPI mTimer = {
|
|
TimerDriverRegisterHandler,
|
|
TimerDriverSetTimerPeriod,
|
|
TimerDriverGetTimerPeriod,
|
|
TimerDriverGenerateSoftInterrupt
|
|
};
|
|
|
|
STATIC EFI_PEI_PPI_DESCRIPTOR mTimerPpiList = {
|
|
(EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST),
|
|
&gH2OTimerArchPpiGuid,
|
|
&mTimer
|
|
};
|
|
|
|
STATIC EFI_PEI_PPI_DESCRIPTOR mEventPpiList = {
|
|
(EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST),
|
|
&gH2OTimerEventPpiGuid,
|
|
NULL
|
|
};
|
|
|
|
/**
|
|
Sets the counter value for Timer #0 in a legacy 8254 timer.
|
|
|
|
@param[in] Count The 16-bit counter value to program into Timer #0 of the legacy 8254 timer.
|
|
|
|
@retval None
|
|
**/
|
|
VOID
|
|
SetPitCount (
|
|
IN UINT16 Count
|
|
)
|
|
{
|
|
UINT8 Data;
|
|
//
|
|
// 0x36 = Read/Write counter LSB then MSB, Mode3 square wave output from this timer.
|
|
// Check register Counter Access Ports Register(0x40/41/42 for counter0/1/2) in PCH B0D31F0
|
|
// check Counter Operating Mode 0~5 at 8254 Timer function description in LPC in EDS.
|
|
//
|
|
Data = 0x36;
|
|
IoWrite8 (TIMER_CONTROL_PORT, Data);
|
|
IoWrite8 (TIMER0_COUNT_PORT, (UINT8) Count);
|
|
IoWrite8 (TIMER0_COUNT_PORT, (UINT8) (Count >> 8));
|
|
}
|
|
|
|
/**
|
|
Get the current ACPI counter's value
|
|
|
|
@param[in] None
|
|
|
|
@retval UINT32 The value of the counter
|
|
**/
|
|
UINT32
|
|
GetAcpiTick (
|
|
VOID
|
|
)
|
|
{
|
|
UINT32 Tick = 0;
|
|
|
|
Tick = IoRead32 ((UINTN) (mPchPmioBase + R_ACPI_IO_PM1_TMR));
|
|
return Tick;
|
|
|
|
}
|
|
|
|
/**
|
|
Measure the 8254 timer interrupt use the ACPI time counter
|
|
|
|
@param[in] TimePeriod The current period of the timer interrupt
|
|
|
|
@retval UINT64 The real system time pass between the sequence 8254 timer
|
|
interrupt
|
|
**/
|
|
UINT64
|
|
MeasureTimeLost (
|
|
IN UINT64 TimePeriod
|
|
)
|
|
{
|
|
UINT32 CurrentTick;
|
|
UINT32 EndTick;
|
|
UINT64 LostTime;
|
|
|
|
CurrentTick = GetAcpiTick ();
|
|
EndTick = CurrentTick;
|
|
|
|
if (CurrentTick < mPreAcpiTick) {
|
|
EndTick = CurrentTick + 0x1000000;
|
|
}
|
|
//
|
|
// The calculation of the lost system time should be very accurate, we use
|
|
// the shift calcu to make sure the value's accurate:
|
|
// the origenal formula is:
|
|
// (EndTick - mPreAcpiTick) * 10,000,000
|
|
// LostTime = -----------------------------------------------
|
|
// (3,579,545 Hz / 1,193,182 Hz) * 1,193,182 Hz
|
|
//
|
|
// Note: the 3,579,545 Hz is the ACPI timer's clock;
|
|
// the 1,193,182 Hz is the 8254 timer's clock;
|
|
//
|
|
LostTime = RShiftU64 (
|
|
MultU64x32 ((UINT64) (EndTick - mPreAcpiTick),
|
|
46869689) + 0x00FFFFFF,
|
|
24
|
|
);
|
|
|
|
if (LostTime != 0) {
|
|
mPreAcpiTick = CurrentTick;
|
|
}
|
|
|
|
return LostTime;
|
|
}
|
|
|
|
/**
|
|
8254 Timer #0 Interrupt Handler
|
|
|
|
@param[in] InterruptType The type of interrupt that occured
|
|
@param[in] SystemContext A pointer to the system context when the interrupt occured
|
|
|
|
@retval None
|
|
**/
|
|
VOID
|
|
TimerInterruptHandler (
|
|
IN CONST EFI_EXCEPTION_TYPE InterruptType,
|
|
IN CONST EFI_SYSTEM_CONTEXT SystemContext
|
|
)
|
|
{
|
|
m8259Ppi->EndOfInterrupt (m8259Ppi, Efi8259Irq0);
|
|
|
|
if (mTimerNotifyFunction) {
|
|
//
|
|
// If we have the timer interrupt miss, then we use
|
|
// the platform ACPI time counter to retrieve the time lost
|
|
//
|
|
mTimerNotifyFunction (MeasureTimeLost (mTimerPeriod));
|
|
}
|
|
}
|
|
|
|
/**
|
|
Reinstall PPI to notify other callback function.
|
|
|
|
@param[in] None
|
|
|
|
@retval None
|
|
**/
|
|
VOID
|
|
ThunkNotifyFunction (
|
|
IN CONST EFI_PEI_SERVICES **PeiServices
|
|
)
|
|
{
|
|
// DEBUG((DEBUG_INFO, "<<--Start SmartTimerPei ThunkNotify-->>\n"));
|
|
(**PeiServices).ReInstallPpi (PeiServices, &mEventPpiList, &mEventPpiList);
|
|
// DEBUG((DEBUG_INFO, "<<--End SmartTimerPei ThunkNotify-->>\n"));
|
|
}
|
|
|
|
/**
|
|
Timer interrupt notify function.
|
|
This notification function to call on every timer interrupt.
|
|
|
|
@param[in] Time The real system time pass between the sequence 8254 timer
|
|
interrupt
|
|
@retval None
|
|
**/
|
|
VOID
|
|
TimerNotifyFunction (
|
|
IN UINT64 Time
|
|
)
|
|
{
|
|
ThunkNotifyFunction (mPeiServices);
|
|
}
|
|
|
|
/**
|
|
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[in] This The H2O_TIMER_ARCH_PPI instance.
|
|
@param[in] NotifyFunction The function to call when a timer interrupt fires. This
|
|
function executes at TPL_HIGH_LEVEL. The DXE Core will
|
|
register a handler for the timer interrupt, so it can know
|
|
how much time has passed. This information is used to
|
|
signal timer based events. NULL will unregister the handler.
|
|
|
|
@retval EFI_SUCCESS The timer handler was registered.
|
|
@exception EFI_UNSUPPORTED The CPU does not support registering a timer interrupt handler
|
|
@retval EFI_ALREADY_STARTED NotifyFunction is not NULL, and a handler is already registered.
|
|
@retval EFI_INVALID_PARAMETER NotifyFunction is NULL, and a handler was not previously registered.
|
|
**/
|
|
EFI_STATUS
|
|
EFIAPI
|
|
TimerDriverRegisterHandler (
|
|
IN H2O_TIMER_ARCH_PPI *This,
|
|
IN H2O_TIMER_NOTIFY NotifyFunction
|
|
)
|
|
{
|
|
//
|
|
// If an attempt is made to unregister a handler when a handler is not registered,
|
|
// then EFI_INVALID_PARAMETER is returned.
|
|
//
|
|
if (mTimerNotifyFunction == NULL && NotifyFunction == NULL) {
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
//
|
|
// If an attempt is made to register a handler
|
|
// when a handler is already registered, then EFI_ALREADY_STARTED is returned.
|
|
//
|
|
if (mTimerNotifyFunction != NULL && NotifyFunction != NULL) {
|
|
return EFI_ALREADY_STARTED;
|
|
}
|
|
//
|
|
// If the CPU does not support registering a timer interrupt handler, then EFI_UNSUPPORTED is returned.
|
|
//
|
|
if (mCpuPpi == NULL || m8259Ppi == NULL) {
|
|
return EFI_UNSUPPORTED;
|
|
}
|
|
|
|
if (NotifyFunction == NULL) {
|
|
//
|
|
// If NotifyFunction is NULL, then the handler is unregistered.
|
|
//
|
|
mTimerNotifyFunction = NULL;
|
|
} else {
|
|
mTimerNotifyFunction = NotifyFunction;
|
|
}
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
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[in] This The H2O_TIMER_ARCH_PPI instance.
|
|
@param[in] TimerPeriod The rate to program the timer interrupt in 100 nS units. If
|
|
the timer hardware is not programmable, then EFI_UNSUPPORTED is
|
|
returned. If the timer is programmable, then the timer period
|
|
will be rounded up to the nearest timer period that is supported
|
|
by the timer hardware. If TimerPeriod is set to 0, then the
|
|
timer interrupts will be disabled.
|
|
|
|
@retval EFI_SUCCESS The timer period was changed.
|
|
**/
|
|
EFI_STATUS
|
|
EFIAPI
|
|
TimerDriverSetTimerPeriod (
|
|
IN H2O_TIMER_ARCH_PPI *This,
|
|
IN UINT64 TimerPeriod
|
|
)
|
|
{
|
|
UINT64 TimerCount;
|
|
|
|
//
|
|
// The basic clock is 1.19318 MHz or 0.119318 ticks per 100 ns.
|
|
// TimerPeriod * 0.119318 = 8254 timer divisor. Using integer arithmetic
|
|
// TimerCount = (TimerPeriod * 119318)/1000000.
|
|
//
|
|
// Round up to next highest integer. This guarantees that the timer is
|
|
// equal to or slightly longer than the requested time.
|
|
// TimerCount = ((TimerPeriod * 119318) + 500000)/1000000
|
|
//
|
|
// Note that a TimerCount of 0 is equivalent to a count of 65,536
|
|
//
|
|
// Since TimerCount is limited to 16 bits for IA32, TimerPeriod is limited
|
|
// to 20 bits.
|
|
//
|
|
if (TimerPeriod == 0) {
|
|
//
|
|
// Disable timer interrupt for a TimerPeriod of 0
|
|
//
|
|
m8259Ppi->DisableIrq (m8259Ppi, Efi8259Irq0);
|
|
} else {
|
|
//
|
|
// Convert TimerPeriod into 8254 counts
|
|
//
|
|
TimerCount = DivU64x32Remainder (MultU64x32 (119318, (UINT32) TimerPeriod) + 500000, 1000000, 0);
|
|
|
|
//
|
|
// Check for overflow
|
|
//
|
|
if (TimerCount >= 65536) {
|
|
TimerCount = 0;
|
|
if (TimerPeriod >= DEFAULT_TIMER_TICK_DURATION) {
|
|
TimerPeriod = DEFAULT_TIMER_TICK_DURATION;
|
|
}
|
|
}
|
|
//
|
|
// Program the 8254 timer with the new count value
|
|
//
|
|
SetPitCount ((UINT16) TimerCount);
|
|
|
|
//
|
|
// Enable timer interrupt
|
|
//
|
|
m8259Ppi->EnableIrq (m8259Ppi, Efi8259Irq0, FALSE);
|
|
}
|
|
//
|
|
// 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
|
|
returned, then the timer is currently disabled.
|
|
|
|
@param[in] This The H2O_TIMER_ARCH_PPI instance.
|
|
@param[out] TimerPeriod A pointer to the timer period to retrieve in 100 ns units.
|
|
If 0 is returned, then the timer is currently disabled.
|
|
|
|
@retval EFI_SUCCESS The timer period was returned in TimerPeriod.
|
|
@retval EFI_INVALID_PARAMETER TimerPeriod is NULL.
|
|
**/
|
|
EFI_STATUS
|
|
EFIAPI
|
|
TimerDriverGetTimerPeriod (
|
|
IN H2O_TIMER_ARCH_PPI *This,
|
|
OUT UINT64 *TimerPeriod
|
|
)
|
|
{
|
|
if (TimerPeriod == NULL) {
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
*TimerPeriod = mTimerPeriod;
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
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 H2O_TIMER_ARCH_PPI.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[in] This The H2O_TIMER_ARCH_PPI instance.
|
|
|
|
@retval EFI_SUCCESS The soft timer interrupt was generated.
|
|
**/
|
|
EFI_STATUS
|
|
EFIAPI
|
|
TimerDriverGenerateSoftInterrupt (
|
|
IN H2O_TIMER_ARCH_PPI *This
|
|
)
|
|
{
|
|
return EFI_UNSUPPORTED;
|
|
}
|
|
|
|
EFI_STATUS
|
|
EFIAPI
|
|
TimerPeiEntry (
|
|
IN EFI_PEI_FILE_HANDLE FileHandle,
|
|
IN CONST EFI_PEI_SERVICES **PeiServices
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
UINT32 TimerVector;
|
|
UINTN Size;
|
|
H2O_PEI_TIMER_DATA_HOB *DataHob;
|
|
|
|
m8259Ppi = NULL;
|
|
mCpuPpi = NULL;
|
|
mPeiServices = PeiServices;
|
|
//
|
|
// Make sure the Timer Architectural Protocol is not already installed in the system
|
|
//
|
|
Status = (**PeiServices).LocatePpi (PeiServices, &gH2OTimerArchPpiGuid, 0, NULL, NULL);
|
|
if (!EFI_ERROR(Status)) {
|
|
return EFI_ALREADY_STARTED;
|
|
}
|
|
|
|
//
|
|
// Find the CPU architectural protocol. ASSERT if not found.
|
|
//
|
|
Status = (**PeiServices).LocatePpi (PeiServices, &gH2OCpuArchPpiGuid, 0, NULL, (VOID**)&mCpuPpi);
|
|
ASSERT_EFI_ERROR (Status);
|
|
|
|
//
|
|
// Find the Legacy8259 protocol. ASSERT if not found.
|
|
//
|
|
Status = (**PeiServices).LocatePpi (PeiServices, &gH2OLegacy8259PpiGuid, 0, NULL, (VOID**)&m8259Ppi);
|
|
ASSERT_EFI_ERROR (Status);
|
|
|
|
mPchPmioBase = PcdGet16 (PcdPerfPkgAcpiIoPortBaseAddress);
|
|
|
|
ASSERT (mPchPmioBase != 0);
|
|
DEBUG ((DEBUG_INFO, "PeiTimer: mPchPmioBase=0x%x\n",mPchPmioBase));
|
|
//
|
|
// Force the timer to be disabled
|
|
//
|
|
Status = TimerDriverSetTimerPeriod (&mTimer, 0);
|
|
ASSERT_EFI_ERROR (Status);
|
|
|
|
//
|
|
// Get the interrupt vector number corresponding to IRQ0 from the 8259 driver
|
|
//
|
|
TimerVector = 0;
|
|
Status = m8259Ppi->GetVector (m8259Ppi, Efi8259Irq0, (UINT8 *) &TimerVector);
|
|
ASSERT_EFI_ERROR (Status);
|
|
|
|
//
|
|
// Install interrupt handler for 8254 Timer #0 (ISA IRQ0)
|
|
//
|
|
Status = mCpuPpi->RegisterInterruptHandler (mCpuPpi, TimerVector, TimerInterruptHandler);
|
|
ASSERT_EFI_ERROR (Status);
|
|
|
|
//
|
|
// Register Timer notify callback function
|
|
//
|
|
TimerDriverRegisterHandler (&mTimer, TimerNotifyFunction);
|
|
|
|
//
|
|
// Pre-install Timer Notify event PPI
|
|
//
|
|
Status = (**PeiServices).InstallPpi (PeiServices, &mEventPpiList);
|
|
ASSERT_EFI_ERROR (Status);
|
|
|
|
//
|
|
// Force the timer to be enabled at its default period
|
|
//
|
|
Status = TimerDriverSetTimerPeriod (&mTimer, DEFAULT_TIMER_TICK_DURATION);
|
|
ASSERT_EFI_ERROR (Status);
|
|
|
|
//
|
|
// Begin the ACPI timer counter
|
|
//
|
|
mPreAcpiTick = GetAcpiTick ();
|
|
|
|
//
|
|
// Install the Timer Architectural Ppi
|
|
//
|
|
Status = (**PeiServices).InstallPpi (PeiServices, &mTimerPpiList);
|
|
ASSERT_EFI_ERROR (Status);
|
|
|
|
//
|
|
// Save the PeiService point and CallbackFunction point
|
|
//
|
|
Size = sizeof (H2O_PEI_TIMER_DATA_HOB);
|
|
DataHob = BuildGuidHob (&gH2OPeiTimerDataHobGuid, Size);
|
|
DataHob->PeiServicesPoint = (UINT32)PeiServices;
|
|
DataHob->CallBackFunction = (UINT32)ThunkNotifyFunction;
|
|
DataHob->MasterBaseVector = PcdGet8(PcdH2OInterruptMasterBaseVector);
|
|
mCpuPpi->EnableInterrupt(mCpuPpi);
|
|
DEBUG ((DEBUG_INFO, "PeiTimer: Create H2OPeiTimerDataHob\n"));
|
|
DEBUG ((DEBUG_INFO, " FunctionPoint = 0x%X\n",DataHob->CallBackFunction));
|
|
DEBUG ((DEBUG_INFO, " PeiServicesPoint = 0x%X\n",DataHob->PeiServicesPoint));
|
|
|
|
return Status;
|
|
}
|