TimerDxe.c revision da9675a241ab9856377b9bd63504b2d5333b3c7a
1/** @file 2 Timer Architecture Protocol driver of the ARM flavor 3 4 Copyright (c) 2011 ARM Ltd. All rights reserved.<BR> 5 6 This program and the accompanying materials 7 are licensed and made available under the terms and conditions of the BSD License 8 which accompanies this distribution. The full text of the license may be found at 9 http://opensource.org/licenses/bsd-license.php 10 11 THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, 12 WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. 13 14**/ 15 16 17#include <PiDxe.h> 18 19#include <Library/BaseLib.h> 20#include <Library/DebugLib.h> 21#include <Library/BaseMemoryLib.h> 22#include <Library/UefiBootServicesTableLib.h> 23#include <Library/UefiLib.h> 24#include <Library/PcdLib.h> 25#include <Library/IoLib.h> 26#include <Library/ArmV7ArchTimerLib.h> 27 28#include <Protocol/Timer.h> 29#include <Protocol/HardwareInterrupt.h> 30 31// The notification function to call on every timer interrupt. 32EFI_TIMER_NOTIFY mTimerNotifyFunction = (EFI_TIMER_NOTIFY)NULL; 33EFI_EVENT EfiExitBootServicesEvent = (EFI_EVENT)NULL; 34 35// The current period of the timer interrupt 36UINT64 mTimerPeriod = 0; 37 38// Cached copy of the Hardware Interrupt protocol instance 39EFI_HARDWARE_INTERRUPT_PROTOCOL *gInterrupt = NULL; 40 41/** 42 This function registers the handler NotifyFunction so it is called every time 43 the timer interrupt fires. It also passes the amount of time since the last 44 handler call to the NotifyFunction. If NotifyFunction is NULL, then the 45 handler is unregistered. If the handler is registered, then EFI_SUCCESS is 46 returned. If the CPU does not support registering a timer interrupt handler, 47 then EFI_UNSUPPORTED is returned. If an attempt is made to register a handler 48 when a handler is already registered, then EFI_ALREADY_STARTED is returned. 49 If an attempt is made to unregister a handler when a handler is not registered, 50 then EFI_INVALID_PARAMETER is returned. If an error occurs attempting to 51 register the NotifyFunction with the timer interrupt, then EFI_DEVICE_ERROR 52 is returned. 53 54 @param This The EFI_TIMER_ARCH_PROTOCOL instance. 55 @param NotifyFunction The function to call when a timer interrupt fires. This 56 function executes at TPL_HIGH_LEVEL. The DXE Core will 57 register a handler for the timer interrupt, so it can know 58 how much time has passed. This information is used to 59 signal timer based events. NULL will unregister the handler. 60 @retval EFI_SUCCESS The timer handler was registered. 61 @retval EFI_UNSUPPORTED The platform does not support timer interrupts. 62 @retval EFI_ALREADY_STARTED NotifyFunction is not NULL, and a handler is already 63 registered. 64 @retval EFI_INVALID_PARAMETER NotifyFunction is NULL, and a handler was not 65 previously registered. 66 @retval EFI_DEVICE_ERROR The timer handler could not be registered. 67 68**/ 69EFI_STATUS 70EFIAPI 71TimerDriverRegisterHandler ( 72 IN EFI_TIMER_ARCH_PROTOCOL *This, 73 IN EFI_TIMER_NOTIFY NotifyFunction 74 ) 75{ 76 if ((NotifyFunction == NULL) && (mTimerNotifyFunction == NULL)) { 77 return EFI_INVALID_PARAMETER; 78 } 79 80 if ((NotifyFunction != NULL) && (mTimerNotifyFunction != NULL)) { 81 return EFI_ALREADY_STARTED; 82 } 83 84 mTimerNotifyFunction = NotifyFunction; 85 86 return EFI_SUCCESS; 87} 88 89/** 90 Disable the timer 91**/ 92VOID 93EFIAPI 94ExitBootServicesEvent ( 95 IN EFI_EVENT Event, 96 IN VOID *Context 97 ) 98{ 99 ArmArchTimerDisableTimer (); 100} 101 102/** 103 104 This function adjusts the period of timer interrupts to the value specified 105 by TimerPeriod. If the timer period is updated, then the selected timer 106 period is stored in EFI_TIMER.TimerPeriod, and EFI_SUCCESS is returned. If 107 the timer hardware is not programmable, then EFI_UNSUPPORTED is returned. 108 If an error occurs while attempting to update the timer period, then the 109 timer hardware will be put back in its state prior to this call, and 110 EFI_DEVICE_ERROR is returned. If TimerPeriod is 0, then the timer interrupt 111 is disabled. This is not the same as disabling the CPU's interrupts. 112 Instead, it must either turn off the timer hardware, or it must adjust the 113 interrupt controller so that a CPU interrupt is not generated when the timer 114 interrupt fires. 115 116 @param This The EFI_TIMER_ARCH_PROTOCOL instance. 117 @param TimerPeriod The rate to program the timer interrupt in 100 nS units. If 118 the timer hardware is not programmable, then EFI_UNSUPPORTED is 119 returned. If the timer is programmable, then the timer period 120 will be rounded up to the nearest timer period that is supported 121 by the timer hardware. If TimerPeriod is set to 0, then the 122 timer interrupts will be disabled. 123 124 125 @retval EFI_SUCCESS The timer period was changed. 126 @retval EFI_UNSUPPORTED The platform cannot change the period of the timer interrupt. 127 @retval EFI_DEVICE_ERROR The timer period could not be changed due to a device error. 128 129**/ 130EFI_STATUS 131EFIAPI 132TimerDriverSetTimerPeriod ( 133 IN EFI_TIMER_ARCH_PROTOCOL *This, 134 IN UINT64 TimerPeriod 135 ) 136{ 137 UINT64 TimerTicks; 138 139 // always disable the timer 140 ArmArchTimerDisableTimer (); 141 142 if (TimerPeriod != 0) { 143 // Convert TimerPeriod to micro sec units 144 TimerTicks = DivU64x32 (TimerPeriod, 10); 145 146 TimerTicks = MultU64x32 (TimerPeriod, (PcdGet32(PcdArmArchTimerFreqInHz)/1000000)); 147 148 ArmArchTimerSetTimerVal((UINTN)TimerTicks); 149 150 // Enable the timer 151 ArmArchTimerEnableTimer (); 152 } 153 154 // Save the new timer period 155 mTimerPeriod = TimerPeriod; 156 return EFI_SUCCESS; 157} 158 159/** 160 This function retrieves the period of timer interrupts in 100 ns units, 161 returns that value in TimerPeriod, and returns EFI_SUCCESS. If TimerPeriod 162 is NULL, then EFI_INVALID_PARAMETER is returned. If a TimerPeriod of 0 is 163 returned, then the timer is currently disabled. 164 165 @param This The EFI_TIMER_ARCH_PROTOCOL instance. 166 @param TimerPeriod A pointer to the timer period to retrieve in 100 ns units. If 167 0 is returned, then the timer is currently disabled. 168 169 170 @retval EFI_SUCCESS The timer period was returned in TimerPeriod. 171 @retval EFI_INVALID_PARAMETER TimerPeriod is NULL. 172 173**/ 174EFI_STATUS 175EFIAPI 176TimerDriverGetTimerPeriod ( 177 IN EFI_TIMER_ARCH_PROTOCOL *This, 178 OUT UINT64 *TimerPeriod 179 ) 180{ 181 if (TimerPeriod == NULL) { 182 return EFI_INVALID_PARAMETER; 183 } 184 185 *TimerPeriod = mTimerPeriod; 186 return EFI_SUCCESS; 187} 188 189/** 190 This function generates a soft timer interrupt. If the platform does not support soft 191 timer interrupts, then EFI_UNSUPPORTED is returned. Otherwise, EFI_SUCCESS is returned. 192 If a handler has been registered through the EFI_TIMER_ARCH_PROTOCOL.RegisterHandler() 193 service, then a soft timer interrupt will be generated. If the timer interrupt is 194 enabled when this service is called, then the registered handler will be invoked. The 195 registered handler should not be able to distinguish a hardware-generated timer 196 interrupt from a software-generated timer interrupt. 197 198 @param This The EFI_TIMER_ARCH_PROTOCOL instance. 199 200 @retval EFI_SUCCESS The soft timer interrupt was generated. 201 @retval EFI_UNSUPPORTED The platform does not support the generation of soft timer interrupts. 202 203**/ 204EFI_STATUS 205EFIAPI 206TimerDriverGenerateSoftInterrupt ( 207 IN EFI_TIMER_ARCH_PROTOCOL *This 208 ) 209{ 210 return EFI_UNSUPPORTED; 211} 212 213/** 214 Interface structure for the Timer Architectural Protocol. 215 216 @par Protocol Description: 217 This protocol provides the services to initialize a periodic timer 218 interrupt, and to register a handler that is called each time the timer 219 interrupt fires. It may also provide a service to adjust the rate of the 220 periodic timer interrupt. When a timer interrupt occurs, the handler is 221 passed the amount of time that has passed since the previous timer 222 interrupt. 223 224 @param RegisterHandler 225 Registers a handler that will be called each time the 226 timer interrupt fires. TimerPeriod defines the minimum 227 time between timer interrupts, so TimerPeriod will also 228 be the minimum time between calls to the registered 229 handler. 230 231 @param SetTimerPeriod 232 Sets the period of the timer interrupt in 100 nS units. 233 This function is optional, and may return EFI_UNSUPPORTED. 234 If this function is supported, then the timer period will 235 be rounded up to the nearest supported timer period. 236 237 238 @param GetTimerPeriod 239 Retrieves the period of the timer interrupt in 100 nS units. 240 241 @param GenerateSoftInterrupt 242 Generates a soft timer interrupt that simulates the firing of 243 the timer interrupt. This service can be used to invoke the registered handler if the timer interrupt has been masked for 244 a period of time. 245 246**/ 247EFI_TIMER_ARCH_PROTOCOL gTimer = { 248 TimerDriverRegisterHandler, 249 TimerDriverSetTimerPeriod, 250 TimerDriverGetTimerPeriod, 251 TimerDriverGenerateSoftInterrupt 252}; 253 254/** 255 256 C Interrupt Handler called in the interrupt context when Source interrupt is active. 257 258 259 @param Source Source of the interrupt. Hardware routing off a specific platform defines 260 what source means. 261 262 @param SystemContext Pointer to system register context. Mostly used by debuggers and will 263 update the system context after the return from the interrupt if 264 modified. Don't change these values unless you know what you are doing 265 266**/ 267VOID 268EFIAPI 269TimerInterruptHandler ( 270 IN HARDWARE_INTERRUPT_SOURCE Source, 271 IN EFI_SYSTEM_CONTEXT SystemContext 272 ) 273{ 274 EFI_TPL OriginalTPL; 275 276 // 277 // DXE core uses this callback for the EFI timer tick. The DXE core uses locks 278 // that raise to TPL_HIGH and then restore back to current level. Thus we need 279 // to make sure TPL level is set to TPL_HIGH while we are handling the timer tick. 280 // 281 OriginalTPL = gBS->RaiseTPL (TPL_HIGH_LEVEL); 282 283 // Check if the timer interrupt is active 284 if ((ArmArchTimerGetTimerCtrlReg () ) & ARM_ARCH_TIMER_ISTATUS) { 285 286 // Signal end of interrupt early to help avoid losing subsequent ticks from long duration handlers 287 gInterrupt->EndOfInterrupt (gInterrupt, Source); 288 289 if (mTimerNotifyFunction) { 290 mTimerNotifyFunction (mTimerPeriod); 291 } 292 293 // Reload the Timer 294 TimerDriverSetTimerPeriod (&gTimer, FixedPcdGet32(PcdTimerPeriod)); 295 } 296 297 // Enable timer interrupts 298 gInterrupt->EnableInterruptSource (gInterrupt, Source); 299 300 gBS->RestoreTPL (OriginalTPL); 301} 302 303 304/** 305 Initialize the state information for the Timer Architectural Protocol and 306 the Timer Debug support protocol that allows the debugger to break into a 307 running program. 308 309 @param ImageHandle of the loaded driver 310 @param SystemTable Pointer to the System Table 311 312 @retval EFI_SUCCESS Protocol registered 313 @retval EFI_OUT_OF_RESOURCES Cannot allocate protocol data structure 314 @retval EFI_DEVICE_ERROR Hardware problems 315 316**/ 317EFI_STATUS 318EFIAPI 319TimerInitialize ( 320 IN EFI_HANDLE ImageHandle, 321 IN EFI_SYSTEM_TABLE *SystemTable 322 ) 323{ 324 EFI_HANDLE Handle = NULL; 325 EFI_STATUS Status; 326 UINTN TimerCtrlReg; 327 328 if (ArmIsArchTimerImplemented () == 0) { 329 DEBUG ((EFI_D_ERROR, "ARM Architectural Timer is not available in the CPU, hence cann't use this Driver \n")); 330 ASSERT (0); 331 } 332 333 // Find the interrupt controller protocol. ASSERT if not found. 334 Status = gBS->LocateProtocol (&gHardwareInterruptProtocolGuid, NULL, (VOID **)&gInterrupt); 335 ASSERT_EFI_ERROR (Status); 336 337 // Disable the timer 338 Status = TimerDriverSetTimerPeriod (&gTimer, 0); 339 ASSERT_EFI_ERROR (Status); 340 341 // Install secure and Non-secure interrupt handlers 342 // Note: Because it is not possible to determine the security state of the 343 // CPU dynamically, we just install interrupt handler for both sec and non-sec 344 // timer PPI 345 Status = gInterrupt->RegisterInterruptSource (gInterrupt, PcdGet32 (PcdArmArchTimerSecIntrNum), TimerInterruptHandler); 346 ASSERT_EFI_ERROR (Status); 347 348 Status = gInterrupt->RegisterInterruptSource (gInterrupt, PcdGet32 (PcdArmArchTimerIntrNum), TimerInterruptHandler); 349 ASSERT_EFI_ERROR (Status); 350 351 // Unmask timer interrupts 352 TimerCtrlReg = ArmArchTimerGetTimerCtrlReg (); 353 TimerCtrlReg &= ~ARM_ARCH_TIMER_IMASK; 354 ArmArchTimerSetTimerCtrlReg (TimerCtrlReg); 355 356 // Set up default timer 357 Status = TimerDriverSetTimerPeriod (&gTimer, FixedPcdGet32(PcdTimerPeriod)); // TIMER_DEFAULT_PERIOD 358 ASSERT_EFI_ERROR (Status); 359 360 // Install the Timer Architectural Protocol onto a new handle 361 Status = gBS->InstallMultipleProtocolInterfaces( 362 &Handle, 363 &gEfiTimerArchProtocolGuid, &gTimer, 364 NULL 365 ); 366 ASSERT_EFI_ERROR(Status); 367 368 // enable Secure timer interrupts 369 Status = gInterrupt->EnableInterruptSource (gInterrupt, PcdGet32 (PcdArmArchTimerSecIntrNum)); 370 371 // enable NonSecure timer interrupts 372 Status = gInterrupt->EnableInterruptSource (gInterrupt, PcdGet32 (PcdArmArchTimerIntrNum)); 373 374 // Register for an ExitBootServicesEvent 375 Status = gBS->CreateEvent (EVT_SIGNAL_EXIT_BOOT_SERVICES, TPL_NOTIFY, ExitBootServicesEvent, NULL, &EfiExitBootServicesEvent); 376 ASSERT_EFI_ERROR (Status); 377 378 return Status; 379} 380