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