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