1 /*++ @file
2   Emu Emulation Timer Architectural Protocol Driver as defined in DXE CIS
3 
4   This Timer module uses an Emu Thread to simulate the timer-tick driven
5   timer service.  In the future, the Thread creation should possibly be
6   abstracted by the CPU architectural protocol
7 
8 Copyright (c) 2004 - 2012, Intel Corporation. All rights reserved.<BR>
9 Portions copyright (c) 2010 - 2011, Apple Inc. All rights reserved.
10 This program and the accompanying materials
11 are licensed and made available under the terms and conditions of the BSD License
12 which accompanies this distribution.  The full text of the license may be found at
13 http://opensource.org/licenses/bsd-license.php
14 
15 THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
16 WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
17 
18 
19 **/
20 
21 #include "PiDxe.h"
22 #include <Protocol/Timer.h>
23 #include <Protocol/Cpu.h>
24 #include "Timer.h"
25 #include <Library/BaseLib.h>
26 #include <Library/DebugLib.h>
27 #include <Library/UefiLib.h>
28 #include <Library/UefiDriverEntryPoint.h>
29 #include <Library/MemoryAllocationLib.h>
30 #include <Library/UefiBootServicesTableLib.h>
31 #include <Library/EmuThunkLib.h>
32 
33 //
34 // Pointer to the CPU Architectural Protocol instance
35 //
36 EFI_CPU_ARCH_PROTOCOL   *mCpu;
37 
38 //
39 // The Timer Architectural Protocol that this driver produces
40 //
41 EFI_TIMER_ARCH_PROTOCOL mTimer = {
42   EmuTimerDriverRegisterHandler,
43   EmuTimerDriverSetTimerPeriod,
44   EmuTimerDriverGetTimerPeriod,
45   EmuTimerDriverGenerateSoftInterrupt
46 };
47 
48 //
49 // The notification function to call on every timer interrupt
50 //
51 EFI_TIMER_NOTIFY        mTimerNotifyFunction = NULL;
52 
53 //
54 // The current period of the timer interrupt
55 //
56 UINT64                  mTimerPeriodMs;
57 
58 
59 VOID
60 EFIAPI
TimerCallback(UINT64 DeltaMs)61 TimerCallback (UINT64 DeltaMs)
62 {
63   EFI_TPL           OriginalTPL;
64   EFI_TIMER_NOTIFY  CallbackFunction;
65 
66 
67   OriginalTPL = gBS->RaiseTPL (TPL_HIGH_LEVEL);
68 
69   if (OriginalTPL < TPL_HIGH_LEVEL) {
70     CallbackFunction = mTimerNotifyFunction;
71 
72     //
73     // Only invoke the callback function if a Non-NULL handler has been
74     // registered. Assume all other handlers are legal.
75     //
76     if (CallbackFunction != NULL) {
77       CallbackFunction (MultU64x32 (DeltaMs, 10000));
78     }
79   }
80 
81   gBS->RestoreTPL (OriginalTPL);
82 
83 }
84 
85 EFI_STATUS
86 EFIAPI
EmuTimerDriverRegisterHandler(IN EFI_TIMER_ARCH_PROTOCOL * This,IN EFI_TIMER_NOTIFY NotifyFunction)87 EmuTimerDriverRegisterHandler (
88   IN EFI_TIMER_ARCH_PROTOCOL           *This,
89   IN EFI_TIMER_NOTIFY                  NotifyFunction
90   )
91 /*++
92 
93 Routine Description:
94 
95   This function registers the handler NotifyFunction so it is called every time
96   the timer interrupt fires.  It also passes the amount of time since the last
97   handler call to the NotifyFunction.  If NotifyFunction is NULL, then the
98   handler is unregistered.  If the handler is registered, then EFI_SUCCESS is
99   returned.  If the CPU does not support registering a timer interrupt handler,
100   then EFI_UNSUPPORTED is returned.  If an attempt is made to register a handler
101   when a handler is already registered, then EFI_ALREADY_STARTED is returned.
102   If an attempt is made to unregister a handler when a handler is not registered,
103   then EFI_INVALID_PARAMETER is returned.  If an error occurs attempting to
104   register the NotifyFunction with the timer interrupt, then EFI_DEVICE_ERROR
105   is returned.
106 
107 Arguments:
108 
109   This           - The EFI_TIMER_ARCH_PROTOCOL instance.
110 
111   NotifyFunction - The function to call when a timer interrupt fires.  This
112                    function executes at TPL_HIGH_LEVEL.  The DXE Core will
113                    register a handler for the timer interrupt, so it can know
114                    how much time has passed.  This information is used to
115                    signal timer based events.  NULL will unregister the handler.
116 
117 Returns:
118 
119   EFI_SUCCESS           - The timer handler was registered.
120 
121   EFI_UNSUPPORTED       - The platform does not support timer interrupts.
122 
123   EFI_ALREADY_STARTED   - NotifyFunction is not NULL, and a handler is already
124                           registered.
125 
126   EFI_INVALID_PARAMETER - NotifyFunction is NULL, and a handler was not
127                           previously registered.
128 
129   EFI_DEVICE_ERROR      - The timer handler could not be registered.
130 
131 **/
132 {
133   //
134   // Check for invalid parameters
135   //
136   if (NotifyFunction == NULL && mTimerNotifyFunction == NULL) {
137     return EFI_INVALID_PARAMETER;
138   }
139 
140   if (NotifyFunction != NULL && mTimerNotifyFunction != NULL) {
141     return EFI_ALREADY_STARTED;
142   }
143 
144   if (NotifyFunction == NULL) {
145     /* Disable timer.  */
146     gEmuThunk->SetTimer (0, TimerCallback);
147   } else if (mTimerNotifyFunction == NULL) {
148     /* Enable Timer.  */
149     gEmuThunk->SetTimer (mTimerPeriodMs, TimerCallback);
150   }
151   mTimerNotifyFunction = NotifyFunction;
152 
153   return EFI_SUCCESS;
154 }
155 
156 EFI_STATUS
157 EFIAPI
EmuTimerDriverSetTimerPeriod(IN EFI_TIMER_ARCH_PROTOCOL * This,IN UINT64 TimerPeriod)158 EmuTimerDriverSetTimerPeriod (
159   IN EFI_TIMER_ARCH_PROTOCOL  *This,
160   IN UINT64                   TimerPeriod
161   )
162 /*++
163 
164 Routine Description:
165 
166   This function adjusts the period of timer interrupts to the value specified
167   by TimerPeriod.  If the timer period is updated, then the selected timer
168   period is stored in EFI_TIMER.TimerPeriod, and EFI_SUCCESS is returned.  If
169   the timer hardware is not programmable, then EFI_UNSUPPORTED is returned.
170   If an error occurs while attempting to update the timer period, then the
171   timer hardware will be put back in its state prior to this call, and
172   EFI_DEVICE_ERROR is returned.  If TimerPeriod is 0, then the timer interrupt
173   is disabled.  This is not the same as disabling the CPU's interrupts.
174   Instead, it must either turn off the timer hardware, or it must adjust the
175   interrupt controller so that a CPU interrupt is not generated when the timer
176   interrupt fires.
177 
178 Arguments:
179 
180   This        - The EFI_TIMER_ARCH_PROTOCOL instance.
181 
182   TimerPeriod - The rate to program the timer interrupt in 100 nS units.  If
183                 the timer hardware is not programmable, then EFI_UNSUPPORTED is
184                 returned.  If the timer is programmable, then the timer period
185                 will be rounded up to the nearest timer period that is supported
186                 by the timer hardware.  If TimerPeriod is set to 0, then the
187                 timer interrupts will be disabled.
188 
189 Returns:
190 
191   EFI_SUCCESS      - The timer period was changed.
192 
193   EFI_UNSUPPORTED  - The platform cannot change the period of the timer interrupt.
194 
195   EFI_DEVICE_ERROR - The timer period could not be changed due to a device error.
196 
197 **/
198 {
199 
200   //
201   // If TimerPeriod is 0, then the timer thread should be canceled
202   // If the TimerPeriod is valid, then create and/or adjust the period of the timer thread
203   //
204   if (TimerPeriod == 0
205       || ((TimerPeriod > TIMER_MINIMUM_VALUE)
206     && (TimerPeriod < TIMER_MAXIMUM_VALUE))) {
207     mTimerPeriodMs = DivU64x32 (TimerPeriod + 5000, 10000);
208 
209     gEmuThunk->SetTimer (mTimerPeriodMs, TimerCallback);
210   }
211 
212   return EFI_SUCCESS;
213 }
214 
215 EFI_STATUS
216 EFIAPI
EmuTimerDriverGetTimerPeriod(IN EFI_TIMER_ARCH_PROTOCOL * This,OUT UINT64 * TimerPeriod)217 EmuTimerDriverGetTimerPeriod (
218   IN EFI_TIMER_ARCH_PROTOCOL            *This,
219   OUT UINT64                            *TimerPeriod
220   )
221 /*++
222 
223 Routine Description:
224 
225   This function retrieves the period of timer interrupts in 100 ns units,
226   returns that value in TimerPeriod, and returns EFI_SUCCESS.  If TimerPeriod
227   is NULL, then EFI_INVALID_PARAMETER is returned.  If a TimerPeriod of 0 is
228   returned, then the timer is currently disabled.
229 
230 Arguments:
231 
232   This        - The EFI_TIMER_ARCH_PROTOCOL instance.
233 
234   TimerPeriod - A pointer to the timer period to retrieve in 100 ns units.  If
235                 0 is returned, then the timer is currently disabled.
236 
237 Returns:
238 
239   EFI_SUCCESS           - The timer period was returned in TimerPeriod.
240 
241   EFI_INVALID_PARAMETER - TimerPeriod is NULL.
242 
243 **/
244 {
245   if (TimerPeriod == NULL) {
246     return EFI_INVALID_PARAMETER;
247   }
248 
249   *TimerPeriod = MultU64x32 (mTimerPeriodMs, 10000);
250 
251   return EFI_SUCCESS;
252 }
253 
254 EFI_STATUS
255 EFIAPI
EmuTimerDriverGenerateSoftInterrupt(IN EFI_TIMER_ARCH_PROTOCOL * This)256 EmuTimerDriverGenerateSoftInterrupt (
257   IN EFI_TIMER_ARCH_PROTOCOL  *This
258   )
259 /*++
260 
261 Routine Description:
262 
263   This function generates a soft timer interrupt. If the platform does not support soft
264   timer interrupts, then EFI_UNSUPPORTED is returned. Otherwise, EFI_SUCCESS is returned.
265   If a handler has been registered through the EFI_TIMER_ARCH_PROTOCOL.RegisterHandler()
266   service, then a soft timer interrupt will be generated. If the timer interrupt is
267   enabled when this service is called, then the registered handler will be invoked. The
268   registered handler should not be able to distinguish a hardware-generated timer
269   interrupt from a software-generated timer interrupt.
270 
271 Arguments:
272 
273   This  -  The EFI_TIMER_ARCH_PROTOCOL instance.
274 
275 Returns:
276 
277   EFI_SUCCESS       - The soft timer interrupt was generated.
278 
279   EFI_UNSUPPORTEDT  - The platform does not support the generation of soft timer interrupts.
280 
281 **/
282 {
283   return EFI_UNSUPPORTED;
284 }
285 
286 EFI_STATUS
287 EFIAPI
EmuTimerDriverInitialize(IN EFI_HANDLE ImageHandle,IN EFI_SYSTEM_TABLE * SystemTable)288 EmuTimerDriverInitialize (
289   IN EFI_HANDLE        ImageHandle,
290   IN EFI_SYSTEM_TABLE  *SystemTable
291   )
292 /*++
293 
294 Routine Description:
295 
296   Initialize the Timer Architectural Protocol driver
297 
298 Arguments:
299 
300   ImageHandle - ImageHandle of the loaded driver
301 
302   SystemTable - Pointer to the System Table
303 
304 Returns:
305 
306   EFI_SUCCESS           - Timer Architectural Protocol created
307 
308   EFI_OUT_OF_RESOURCES  - Not enough resources available to initialize driver.
309 
310   EFI_DEVICE_ERROR      - A device error occured attempting to initialize the driver.
311 
312 **/
313 {
314   EFI_STATUS  Status;
315   EFI_HANDLE  Handle;
316 
317   //
318   // Make sure the Timer Architectural Protocol is not already installed in the system
319   //
320   ASSERT_PROTOCOL_ALREADY_INSTALLED (NULL, &gEfiTimerArchProtocolGuid);
321 
322   //
323   // Get the CPU Architectural Protocol instance
324   //
325   Status = gBS->LocateProtocol (&gEfiCpuArchProtocolGuid, NULL, (void *)&mCpu);
326   ASSERT_EFI_ERROR (Status);
327 
328   //
329   // Start the timer thread at the default timer period
330   //
331   Status = mTimer.SetTimerPeriod (&mTimer, DEFAULT_TIMER_TICK_DURATION);
332   if (EFI_ERROR (Status)) {
333     return Status;
334   }
335 
336   //
337   // Install the Timer Architectural Protocol onto a new handle
338   //
339   Handle = NULL;
340   Status = gBS->InstallProtocolInterface (
341                   &Handle,
342                   &gEfiTimerArchProtocolGuid,
343                   EFI_NATIVE_INTERFACE,
344                   &mTimer
345                   );
346   if (EFI_ERROR (Status)) {
347     return Status;
348   }
349 
350 
351   return EFI_SUCCESS;
352 }
353