[efi] Use a timer event to generate the currticks() timer
We currently use the EFI_CPU_ARCH_PROTOCOL's GetTimerValue() method to generate the currticks() timer, calibrated against a 1ms delay from the boot services Stall() method. This does not work on ARM platforms, where GetTimerValue() is an empty stub which just returns EFI_UNSUPPORTED. Fix by instead creating a periodic timer event, and using this event to increment a current tick counter. Signed-off-by: Michael Brown <mcb30@ipxe.org>
This commit is contained in:
parent
1e066431a4
commit
757ab98381
|
@ -1,302 +0,0 @@
|
|||
/** @file
|
||||
CPU Architectural Protocol as defined in PI spec Volume 2 DXE
|
||||
|
||||
This code abstracts the DXE core from processor implementation details.
|
||||
|
||||
Copyright (c) 2006 - 2011, Intel Corporation. All rights reserved.<BR>
|
||||
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.
|
||||
|
||||
**/
|
||||
|
||||
#ifndef __ARCH_PROTOCOL_CPU_H__
|
||||
#define __ARCH_PROTOCOL_CPU_H__
|
||||
|
||||
FILE_LICENCE ( BSD3 );
|
||||
|
||||
#include <ipxe/efi/Protocol/DebugSupport.h>
|
||||
|
||||
#define EFI_CPU_ARCH_PROTOCOL_GUID \
|
||||
{ 0x26baccb1, 0x6f42, 0x11d4, {0xbc, 0xe7, 0x0, 0x80, 0xc7, 0x3c, 0x88, 0x81 } }
|
||||
|
||||
typedef struct _EFI_CPU_ARCH_PROTOCOL EFI_CPU_ARCH_PROTOCOL;
|
||||
|
||||
///
|
||||
/// The type of flush operation
|
||||
///
|
||||
typedef enum {
|
||||
EfiCpuFlushTypeWriteBackInvalidate,
|
||||
EfiCpuFlushTypeWriteBack,
|
||||
EfiCpuFlushTypeInvalidate,
|
||||
EfiCpuMaxFlushType
|
||||
} EFI_CPU_FLUSH_TYPE;
|
||||
|
||||
///
|
||||
/// The type of processor INIT.
|
||||
///
|
||||
typedef enum {
|
||||
EfiCpuInit,
|
||||
EfiCpuMaxInitType
|
||||
} EFI_CPU_INIT_TYPE;
|
||||
|
||||
/**
|
||||
EFI_CPU_INTERRUPT_HANDLER that is called when a processor interrupt occurs.
|
||||
|
||||
@param InterruptType Defines the type of interrupt or exception that
|
||||
occurred on the processor.This parameter is processor architecture specific.
|
||||
@param SystemContext A pointer to the processor context when
|
||||
the interrupt occurred on the processor.
|
||||
|
||||
@return None
|
||||
|
||||
**/
|
||||
typedef
|
||||
VOID
|
||||
(EFIAPI *EFI_CPU_INTERRUPT_HANDLER)(
|
||||
IN CONST EFI_EXCEPTION_TYPE InterruptType,
|
||||
IN CONST EFI_SYSTEM_CONTEXT SystemContext
|
||||
);
|
||||
|
||||
/**
|
||||
This function flushes the range of addresses from Start to Start+Length
|
||||
from the processor's data cache. If Start is not aligned to a cache line
|
||||
boundary, then the bytes before Start to the preceding cache line boundary
|
||||
are also flushed. If Start+Length is not aligned to a cache line boundary,
|
||||
then the bytes past Start+Length to the end of the next cache line boundary
|
||||
are also flushed. The FlushType of EfiCpuFlushTypeWriteBackInvalidate must be
|
||||
supported. If the data cache is fully coherent with all DMA operations, then
|
||||
this function can just return EFI_SUCCESS. If the processor does not support
|
||||
flushing a range of the data cache, then the entire data cache can be flushed.
|
||||
|
||||
@param This The EFI_CPU_ARCH_PROTOCOL instance.
|
||||
@param Start The beginning physical address to flush from the processor's data
|
||||
cache.
|
||||
@param Length The number of bytes to flush from the processor's data cache. This
|
||||
function may flush more bytes than Length specifies depending upon
|
||||
the granularity of the flush operation that the processor supports.
|
||||
@param FlushType Specifies the type of flush operation to perform.
|
||||
|
||||
@retval EFI_SUCCESS The address range from Start to Start+Length was flushed from
|
||||
the processor's data cache.
|
||||
@retval EFI_UNSUPPORTEDT The processor does not support the cache flush type specified
|
||||
by FlushType.
|
||||
@retval EFI_DEVICE_ERROR The address range from Start to Start+Length could not be flushed
|
||||
from the processor's data cache.
|
||||
|
||||
**/
|
||||
typedef
|
||||
EFI_STATUS
|
||||
(EFIAPI *EFI_CPU_FLUSH_DATA_CACHE)(
|
||||
IN EFI_CPU_ARCH_PROTOCOL *This,
|
||||
IN EFI_PHYSICAL_ADDRESS Start,
|
||||
IN UINT64 Length,
|
||||
IN EFI_CPU_FLUSH_TYPE FlushType
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
This function enables interrupt processing by the processor.
|
||||
|
||||
@param This The EFI_CPU_ARCH_PROTOCOL instance.
|
||||
|
||||
@retval EFI_SUCCESS Interrupts are enabled on the processor.
|
||||
@retval EFI_DEVICE_ERROR Interrupts could not be enabled on the processor.
|
||||
|
||||
**/
|
||||
typedef
|
||||
EFI_STATUS
|
||||
(EFIAPI *EFI_CPU_ENABLE_INTERRUPT)(
|
||||
IN EFI_CPU_ARCH_PROTOCOL *This
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
This function disables interrupt processing by the processor.
|
||||
|
||||
@param This The EFI_CPU_ARCH_PROTOCOL instance.
|
||||
|
||||
@retval EFI_SUCCESS Interrupts are disabled on the processor.
|
||||
@retval EFI_DEVICE_ERROR Interrupts could not be disabled on the processor.
|
||||
|
||||
**/
|
||||
typedef
|
||||
EFI_STATUS
|
||||
(EFIAPI *EFI_CPU_DISABLE_INTERRUPT)(
|
||||
IN EFI_CPU_ARCH_PROTOCOL *This
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
This function retrieves the processor's current interrupt state a returns it in
|
||||
State. If interrupts are currently enabled, then TRUE is returned. If interrupts
|
||||
are currently disabled, then FALSE is returned.
|
||||
|
||||
@param This The EFI_CPU_ARCH_PROTOCOL instance.
|
||||
@param State A pointer to the processor's current interrupt state. Set to TRUE if
|
||||
interrupts are enabled and FALSE if interrupts are disabled.
|
||||
|
||||
@retval EFI_SUCCESS The processor's current interrupt state was returned in State.
|
||||
@retval EFI_INVALID_PARAMETER State is NULL.
|
||||
|
||||
**/
|
||||
typedef
|
||||
EFI_STATUS
|
||||
(EFIAPI *EFI_CPU_GET_INTERRUPT_STATE)(
|
||||
IN EFI_CPU_ARCH_PROTOCOL *This,
|
||||
OUT BOOLEAN *State
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
This function generates an INIT on the processor. If this function succeeds, then the
|
||||
processor will be reset, and control will not be returned to the caller. If InitType is
|
||||
not supported by this processor, or the processor cannot programmatically generate an
|
||||
INIT without help from external hardware, then EFI_UNSUPPORTED is returned. If an error
|
||||
occurs attempting to generate an INIT, then EFI_DEVICE_ERROR is returned.
|
||||
|
||||
@param This The EFI_CPU_ARCH_PROTOCOL instance.
|
||||
@param InitType The type of processor INIT to perform.
|
||||
|
||||
@retval EFI_SUCCESS The processor INIT was performed. This return code should never be seen.
|
||||
@retval EFI_UNSUPPORTED The processor INIT operation specified by InitType is not supported
|
||||
by this processor.
|
||||
@retval EFI_DEVICE_ERROR The processor INIT failed.
|
||||
|
||||
**/
|
||||
typedef
|
||||
EFI_STATUS
|
||||
(EFIAPI *EFI_CPU_INIT)(
|
||||
IN EFI_CPU_ARCH_PROTOCOL *This,
|
||||
IN EFI_CPU_INIT_TYPE InitType
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
This function registers and enables the handler specified by InterruptHandler for a processor
|
||||
interrupt or exception type specified by InterruptType. If InterruptHandler is NULL, then the
|
||||
handler for the processor interrupt or exception type specified by InterruptType is uninstalled.
|
||||
The installed handler is called once for each processor interrupt or exception.
|
||||
|
||||
@param This The EFI_CPU_ARCH_PROTOCOL instance.
|
||||
@param InterruptType A pointer to the processor's current interrupt state. Set to TRUE if interrupts
|
||||
are enabled and FALSE if interrupts are disabled.
|
||||
@param InterruptHandler A pointer to a function of type EFI_CPU_INTERRUPT_HANDLER that is called
|
||||
when a processor interrupt occurs. If this parameter is NULL, then the handler
|
||||
will be uninstalled.
|
||||
|
||||
@retval EFI_SUCCESS The handler for the processor interrupt was successfully installed or uninstalled.
|
||||
@retval EFI_ALREADY_STARTED InterruptHandler is not NULL, and a handler for InterruptType was
|
||||
previously installed.
|
||||
@retval EFI_INVALID_PARAMETER InterruptHandler is NULL, and a handler for InterruptType was not
|
||||
previously installed.
|
||||
@retval EFI_UNSUPPORTED The interrupt specified by InterruptType is not supported.
|
||||
|
||||
**/
|
||||
typedef
|
||||
EFI_STATUS
|
||||
(EFIAPI *EFI_CPU_REGISTER_INTERRUPT_HANDLER)(
|
||||
IN EFI_CPU_ARCH_PROTOCOL *This,
|
||||
IN EFI_EXCEPTION_TYPE InterruptType,
|
||||
IN EFI_CPU_INTERRUPT_HANDLER InterruptHandler
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
This function reads the processor timer specified by TimerIndex and returns it in TimerValue.
|
||||
|
||||
@param This The EFI_CPU_ARCH_PROTOCOL instance.
|
||||
@param TimerIndex Specifies which processor timer is to be returned in TimerValue. This parameter
|
||||
must be between 0 and NumberOfTimers-1.
|
||||
@param TimerValue Pointer to the returned timer value.
|
||||
@param TimerPeriod A pointer to the amount of time that passes in femtoseconds for each increment
|
||||
of TimerValue. If TimerValue does not increment at a predictable rate, then 0 is
|
||||
returned. This parameter is optional and may be NULL.
|
||||
|
||||
@retval EFI_SUCCESS The processor timer value specified by TimerIndex was returned in TimerValue.
|
||||
@retval EFI_DEVICE_ERROR An error occurred attempting to read one of the processor's timers.
|
||||
@retval EFI_INVALID_PARAMETER TimerValue is NULL or TimerIndex is not valid.
|
||||
@retval EFI_UNSUPPORTED The processor does not have any readable timers.
|
||||
|
||||
**/
|
||||
typedef
|
||||
EFI_STATUS
|
||||
(EFIAPI *EFI_CPU_GET_TIMER_VALUE)(
|
||||
IN EFI_CPU_ARCH_PROTOCOL *This,
|
||||
IN UINT32 TimerIndex,
|
||||
OUT UINT64 *TimerValue,
|
||||
OUT UINT64 *TimerPeriod OPTIONAL
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
This function modifies the attributes for the memory region specified by BaseAddress and
|
||||
Length from their current attributes to the attributes specified by Attributes.
|
||||
|
||||
@param This The EFI_CPU_ARCH_PROTOCOL instance.
|
||||
@param BaseAddress The physical address that is the start address of a memory region.
|
||||
@param Length The size in bytes of the memory region.
|
||||
@param Attributes The bit mask of attributes to set for the memory region.
|
||||
|
||||
@retval EFI_SUCCESS The attributes were set for the memory region.
|
||||
@retval EFI_ACCESS_DENIED The attributes for the memory resource range specified by
|
||||
BaseAddress and Length cannot be modified.
|
||||
@retval EFI_INVALID_PARAMETER Length is zero.
|
||||
Attributes specified an illegal combination of attributes that
|
||||
cannot be set together.
|
||||
@retval EFI_OUT_OF_RESOURCES There are not enough system resources to modify the attributes of
|
||||
the memory resource range.
|
||||
@retval EFI_UNSUPPORTED The processor does not support one or more bytes of the memory
|
||||
resource range specified by BaseAddress and Length.
|
||||
The bit mask of attributes is not support for the memory resource
|
||||
range specified by BaseAddress and Length.
|
||||
|
||||
**/
|
||||
typedef
|
||||
EFI_STATUS
|
||||
(EFIAPI *EFI_CPU_SET_MEMORY_ATTRIBUTES)(
|
||||
IN EFI_CPU_ARCH_PROTOCOL *This,
|
||||
IN EFI_PHYSICAL_ADDRESS BaseAddress,
|
||||
IN UINT64 Length,
|
||||
IN UINT64 Attributes
|
||||
);
|
||||
|
||||
|
||||
///
|
||||
/// The EFI_CPU_ARCH_PROTOCOL is used to abstract processor-specific functions from the DXE
|
||||
/// Foundation. This includes flushing caches, enabling and disabling interrupts, hooking interrupt
|
||||
/// vectors and exception vectors, reading internal processor timers, resetting the processor, and
|
||||
/// determining the processor frequency.
|
||||
///
|
||||
struct _EFI_CPU_ARCH_PROTOCOL {
|
||||
EFI_CPU_FLUSH_DATA_CACHE FlushDataCache;
|
||||
EFI_CPU_ENABLE_INTERRUPT EnableInterrupt;
|
||||
EFI_CPU_DISABLE_INTERRUPT DisableInterrupt;
|
||||
EFI_CPU_GET_INTERRUPT_STATE GetInterruptState;
|
||||
EFI_CPU_INIT Init;
|
||||
EFI_CPU_REGISTER_INTERRUPT_HANDLER RegisterInterruptHandler;
|
||||
EFI_CPU_GET_TIMER_VALUE GetTimerValue;
|
||||
EFI_CPU_SET_MEMORY_ATTRIBUTES SetMemoryAttributes;
|
||||
///
|
||||
/// The number of timers that are available in a processor. The value in this
|
||||
/// field is a constant that must not be modified after the CPU Architectural
|
||||
/// Protocol is installed. All consumers must treat this as a read-only field.
|
||||
///
|
||||
UINT32 NumberOfTimers;
|
||||
///
|
||||
/// The size, in bytes, of the alignment required for DMA buffer allocations.
|
||||
/// This is typically the size of the largest data cache line in the platform.
|
||||
/// The value in this field is a constant that must not be modified after the
|
||||
/// CPU Architectural Protocol is installed. All consumers must treat this as
|
||||
/// a read-only field.
|
||||
///
|
||||
UINT32 DmaBufferAlignment;
|
||||
};
|
||||
|
||||
extern EFI_GUID gEfiCpuArchProtocolGuid;
|
||||
|
||||
#endif
|
|
@ -15,4 +15,22 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
|||
#define TIMER_PREFIX_efi __efi_
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Number of ticks per second
|
||||
*
|
||||
* This is a policy decision.
|
||||
*/
|
||||
#define EFI_TICKS_PER_SEC 20
|
||||
|
||||
/**
|
||||
* Get number of ticks per second
|
||||
*
|
||||
* @ret ticks_per_sec Number of ticks per second
|
||||
*/
|
||||
static inline __attribute__ (( always_inline )) unsigned long
|
||||
TIMER_INLINE ( efi, ticks_per_sec ) ( void ) {
|
||||
|
||||
return EFI_TICKS_PER_SEC;
|
||||
}
|
||||
|
||||
#endif /* _IPXE_EFI_TIMER_H */
|
||||
|
|
|
@ -25,12 +25,10 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
|||
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <assert.h>
|
||||
#include <unistd.h>
|
||||
#include <ipxe/timer.h>
|
||||
#include <ipxe/init.h>
|
||||
#include <ipxe/efi/efi.h>
|
||||
#include <ipxe/efi/Protocol/Cpu.h>
|
||||
|
||||
/** @file
|
||||
*
|
||||
|
@ -38,19 +36,14 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
|||
*
|
||||
*/
|
||||
|
||||
/** Scale factor to apply to CPU timer 0
|
||||
*
|
||||
* The timer is scaled down in order to ensure that reasonable values
|
||||
* for "number of ticks" don't exceed the size of an unsigned long.
|
||||
*/
|
||||
#define EFI_TIMER0_SHIFT 12
|
||||
/** Current tick count */
|
||||
static unsigned long efi_jiffies;
|
||||
|
||||
/** Calibration time */
|
||||
#define EFI_CALIBRATE_DELAY_MS 1
|
||||
/** Timer tick event */
|
||||
static EFI_EVENT efi_tick_event;
|
||||
|
||||
/** CPU protocol */
|
||||
static EFI_CPU_ARCH_PROTOCOL *cpu_arch;
|
||||
EFI_REQUIRE_PROTOCOL ( EFI_CPU_ARCH_PROTOCOL, &cpu_arch );
|
||||
/** Colour for debug messages */
|
||||
#define colour &efi_jiffies
|
||||
|
||||
/**
|
||||
* Delay for a fixed number of microseconds
|
||||
|
@ -64,8 +57,8 @@ static void efi_udelay ( unsigned long usecs ) {
|
|||
|
||||
if ( ( efirc = bs->Stall ( usecs ) ) != 0 ) {
|
||||
rc = -EEFI ( efirc );
|
||||
DBG ( "EFI could not delay for %ldus: %s\n",
|
||||
usecs, strerror ( rc ) );
|
||||
DBGC ( colour, "EFI could not delay for %ldus: %s\n",
|
||||
usecs, strerror ( rc ) );
|
||||
/* Probably screwed */
|
||||
}
|
||||
}
|
||||
|
@ -76,53 +69,78 @@ static void efi_udelay ( unsigned long usecs ) {
|
|||
* @ret ticks Current time, in ticks
|
||||
*/
|
||||
static unsigned long efi_currticks ( void ) {
|
||||
UINT64 time;
|
||||
EFI_STATUS efirc;
|
||||
int rc;
|
||||
|
||||
/* Read CPU timer 0 (TSC) */
|
||||
if ( ( efirc = cpu_arch->GetTimerValue ( cpu_arch, 0, &time,
|
||||
NULL ) ) != 0 ) {
|
||||
rc = -EEFI ( efirc );
|
||||
DBG ( "EFI could not read CPU timer: %s\n", strerror ( rc ) );
|
||||
/* Probably screwed */
|
||||
return -1UL;
|
||||
}
|
||||
|
||||
return ( time >> EFI_TIMER0_SHIFT );
|
||||
return efi_jiffies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get number of ticks per second
|
||||
* Timer tick
|
||||
*
|
||||
* @ret ticks_per_sec Number of ticks per second
|
||||
* @v event Timer tick event
|
||||
* @v context Event context
|
||||
*/
|
||||
static unsigned long efi_ticks_per_sec ( void ) {
|
||||
static unsigned long ticks_per_sec = 0;
|
||||
static EFIAPI void efi_tick ( EFI_EVENT event __unused,
|
||||
void *context __unused ) {
|
||||
|
||||
/* Calibrate timer, if necessary. EFI does nominally provide
|
||||
* the timer speed via the (optional) TimerPeriod parameter to
|
||||
* the GetTimerValue() call, but it gets the speed slightly
|
||||
* wrong. By up to three orders of magnitude. Not helpful.
|
||||
*/
|
||||
if ( ! ticks_per_sec ) {
|
||||
unsigned long start;
|
||||
unsigned long elapsed;
|
||||
/* Increment tick count */
|
||||
efi_jiffies++;
|
||||
}
|
||||
|
||||
DBG ( "Calibrating EFI timer with a %d ms delay\n",
|
||||
EFI_CALIBRATE_DELAY_MS );
|
||||
start = currticks();
|
||||
mdelay ( EFI_CALIBRATE_DELAY_MS );
|
||||
elapsed = ( currticks() - start );
|
||||
ticks_per_sec = ( elapsed * ( 1000 / EFI_CALIBRATE_DELAY_MS ));
|
||||
DBG ( "EFI CPU timer calibrated at %ld ticks in %d ms (%ld "
|
||||
"ticks/sec)\n", elapsed, EFI_CALIBRATE_DELAY_MS,
|
||||
ticks_per_sec );
|
||||
/**
|
||||
* Start timer tick
|
||||
*
|
||||
*/
|
||||
static void efi_tick_startup ( void ) {
|
||||
EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
|
||||
EFI_STATUS efirc;
|
||||
int rc;
|
||||
|
||||
/* Create timer tick event */
|
||||
if ( ( efirc = bs->CreateEvent ( ( EVT_TIMER | EVT_NOTIFY_SIGNAL ),
|
||||
TPL_CALLBACK, efi_tick, NULL,
|
||||
&efi_tick_event ) ) != 0 ) {
|
||||
rc = -EEFI ( efirc );
|
||||
DBGC ( colour, "EFI could not create timer tick: %s\n",
|
||||
strerror ( rc ) );
|
||||
/* Nothing we can do about it */
|
||||
return;
|
||||
}
|
||||
|
||||
return ticks_per_sec;
|
||||
/* Start timer tick */
|
||||
if ( ( efirc = bs->SetTimer ( efi_tick_event, TimerPeriodic,
|
||||
( 10000000 / EFI_TICKS_PER_SEC ) ) ) !=0){
|
||||
rc = -EEFI ( efirc );
|
||||
DBGC ( colour, "EFI could not start timer tick: %s\n",
|
||||
strerror ( rc ) );
|
||||
/* Nothing we can do about it */
|
||||
return;
|
||||
}
|
||||
DBGC ( colour, "EFI timer started at %d ticks per second\n",
|
||||
EFI_TICKS_PER_SEC );
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop timer tick
|
||||
*
|
||||
* @v booting System is shutting down in order to boot
|
||||
*/
|
||||
static void efi_tick_shutdown ( int booting __unused ) {
|
||||
EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
|
||||
|
||||
/* Stop timer tick */
|
||||
bs->SetTimer ( efi_tick_event, TimerCancel, 0 );
|
||||
DBGC ( colour, "EFI timer stopped\n" );
|
||||
|
||||
/* Destroy timer tick event */
|
||||
bs->CloseEvent ( efi_tick_event );
|
||||
}
|
||||
|
||||
/** Timer tick startup function */
|
||||
struct startup_fn efi_tick_startup_fn __startup_fn ( STARTUP_EARLY ) = {
|
||||
.startup = efi_tick_startup,
|
||||
.shutdown = efi_tick_shutdown,
|
||||
};
|
||||
|
||||
PROVIDE_TIMER ( efi, udelay, efi_udelay );
|
||||
PROVIDE_TIMER ( efi, currticks, efi_currticks );
|
||||
PROVIDE_TIMER ( efi, ticks_per_sec, efi_ticks_per_sec );
|
||||
PROVIDE_TIMER_INLINE ( efi, ticks_per_sec );
|
||||
|
|
Reference in New Issue