• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /** @file
2   Timer Architectural Protocol as defined in the DXE CIS
3 
4 Copyright (c) 2005 - 2012, Intel Corporation. All rights reserved.<BR>
5 This program and the accompanying materials
6 are licensed and made available under the terms and conditions of the BSD License
7 which accompanies this distribution.  The full text of the license may be found at
8 http://opensource.org/licenses/bsd-license.php
9 
10 THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
11 WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
12 
13 **/
14 
15 #include "Timer.h"
16 
17 //
18 // The handle onto which the Timer Architectural Protocol will be installed
19 //
20 EFI_HANDLE                mTimerHandle = NULL;
21 
22 //
23 // The Timer Architectural Protocol that this driver produces
24 //
25 EFI_TIMER_ARCH_PROTOCOL   mTimer = {
26   TimerDriverRegisterHandler,
27   TimerDriverSetTimerPeriod,
28   TimerDriverGetTimerPeriod,
29   TimerDriverGenerateSoftInterrupt
30 };
31 
32 //
33 // Pointer to the CPU Architectural Protocol instance
34 //
35 EFI_CPU_ARCH_PROTOCOL     *mCpu;
36 
37 //
38 // Pointer to the Legacy 8259 Protocol instance
39 //
40 EFI_LEGACY_8259_PROTOCOL  *mLegacy8259;
41 
42 //
43 // The notification function to call on every timer interrupt.
44 // A bug in the compiler prevents us from initializing this here.
45 //
46 EFI_TIMER_NOTIFY mTimerNotifyFunction;
47 
48 //
49 // The current period of the timer interrupt
50 //
51 volatile UINT64           mTimerPeriod = 0;
52 
53 //
54 // Worker Functions
55 //
56 /**
57   Sets the counter value for Timer #0 in a legacy 8254 timer.
58 
59   @param Count    The 16-bit counter value to program into Timer #0 of the legacy 8254 timer.
60 **/
61 VOID
SetPitCount(IN UINT16 Count)62 SetPitCount (
63   IN UINT16  Count
64   )
65 {
66   IoWrite8 (TIMER_CONTROL_PORT, 0x36);
67   IoWrite8 (TIMER0_COUNT_PORT, (UINT8)(Count & 0xff));
68   IoWrite8 (TIMER0_COUNT_PORT, (UINT8)((Count >> 8) & 0xff));
69 }
70 
71 /**
72   8254 Timer #0 Interrupt Handler.
73 
74   @param InterruptType    The type of interrupt that occured
75   @param SystemContext    A pointer to the system context when the interrupt occured
76 **/
77 VOID
78 EFIAPI
TimerInterruptHandler(IN EFI_EXCEPTION_TYPE InterruptType,IN EFI_SYSTEM_CONTEXT SystemContext)79 TimerInterruptHandler (
80   IN EFI_EXCEPTION_TYPE   InterruptType,
81   IN EFI_SYSTEM_CONTEXT   SystemContext
82   )
83 {
84   EFI_TPL OriginalTPL;
85 
86   OriginalTPL = gBS->RaiseTPL (TPL_HIGH_LEVEL);
87 
88   mLegacy8259->EndOfInterrupt (mLegacy8259, Efi8259Irq0);
89 
90   if (mTimerNotifyFunction != NULL) {
91     //
92     // @bug : This does not handle missed timer interrupts
93     //
94     mTimerNotifyFunction (mTimerPeriod);
95   }
96 
97   gBS->RestoreTPL (OriginalTPL);
98 }
99 
100 /**
101 
102   This function registers the handler NotifyFunction so it is called every time
103   the timer interrupt fires.  It also passes the amount of time since the last
104   handler call to the NotifyFunction.  If NotifyFunction is NULL, then the
105   handler is unregistered.  If the handler is registered, then EFI_SUCCESS is
106   returned.  If the CPU does not support registering a timer interrupt handler,
107   then EFI_UNSUPPORTED is returned.  If an attempt is made to register a handler
108   when a handler is already registered, then EFI_ALREADY_STARTED is returned.
109   If an attempt is made to unregister a handler when a handler is not registered,
110   then EFI_INVALID_PARAMETER is returned.  If an error occurs attempting to
111   register the NotifyFunction with the timer interrupt, then EFI_DEVICE_ERROR
112   is returned.
113 
114 
115   @param This             The EFI_TIMER_ARCH_PROTOCOL instance.
116   @param NotifyFunction   The function to call when a timer interrupt fires.  This
117                           function executes at TPL_HIGH_LEVEL.  The DXE Core will
118                           register a handler for the timer interrupt, so it can know
119                           how much time has passed.  This information is used to
120                           signal timer based events.  NULL will unregister the handler.
121 
122   @retval        EFI_SUCCESS            The timer handler was registered.
123   @retval        EFI_UNSUPPORTED        The platform does not support timer interrupts.
124   @retval        EFI_ALREADY_STARTED    NotifyFunction is not NULL, and a handler is already
125                                         registered.
126   @retval        EFI_INVALID_PARAMETER  NotifyFunction is NULL, and a handler was not
127                                         previously registered.
128   @retval        EFI_DEVICE_ERROR       The timer handler could not be registered.
129 
130 **/
131 EFI_STATUS
132 EFIAPI
TimerDriverRegisterHandler(IN EFI_TIMER_ARCH_PROTOCOL * This,IN EFI_TIMER_NOTIFY NotifyFunction)133 TimerDriverRegisterHandler (
134   IN EFI_TIMER_ARCH_PROTOCOL  *This,
135   IN EFI_TIMER_NOTIFY         NotifyFunction
136   )
137 {
138   //
139   // Check for invalid parameters
140   //
141   if (NotifyFunction == NULL && mTimerNotifyFunction == NULL) {
142     return EFI_INVALID_PARAMETER;
143   }
144 
145   if (NotifyFunction != NULL && mTimerNotifyFunction != NULL) {
146     return EFI_ALREADY_STARTED;
147   }
148 
149   mTimerNotifyFunction = NotifyFunction;
150 
151   return EFI_SUCCESS;
152 }
153 
154 /**
155 
156   This function adjusts the period of timer interrupts to the value specified
157   by TimerPeriod.  If the timer period is updated, then the selected timer
158   period is stored in EFI_TIMER.TimerPeriod, and EFI_SUCCESS is returned.  If
159   the timer hardware is not programmable, then EFI_UNSUPPORTED is returned.
160   If an error occurs while attempting to update the timer period, then the
161   timer hardware will be put back in its state prior to this call, and
162   EFI_DEVICE_ERROR is returned.  If TimerPeriod is 0, then the timer interrupt
163   is disabled.  This is not the same as disabling the CPU's interrupts.
164   Instead, it must either turn off the timer hardware, or it must adjust the
165   interrupt controller so that a CPU interrupt is not generated when the timer
166   interrupt fires.
167 
168 
169   @param This            The EFI_TIMER_ARCH_PROTOCOL instance.
170   @param TimerPeriod     The rate to program the timer interrupt in 100 nS units.  If
171                          the timer hardware is not programmable, then EFI_UNSUPPORTED is
172                          returned.  If the timer is programmable, then the timer period
173                          will be rounded up to the nearest timer period that is supported
174                          by the timer hardware.  If TimerPeriod is set to 0, then the
175                          timer interrupts will be disabled.
176 
177   @retval        EFI_SUCCESS       The timer period was changed.
178   @retval        EFI_UNSUPPORTED   The platform cannot change the period of the timer interrupt.
179   @retval        EFI_DEVICE_ERROR  The timer period could not be changed due to a device error.
180 
181 **/
182 EFI_STATUS
183 EFIAPI
TimerDriverSetTimerPeriod(IN EFI_TIMER_ARCH_PROTOCOL * This,IN UINT64 TimerPeriod)184 TimerDriverSetTimerPeriod (
185   IN EFI_TIMER_ARCH_PROTOCOL  *This,
186   IN UINT64                   TimerPeriod
187   )
188 {
189   UINT64  TimerCount;
190 
191   //
192   //  The basic clock is 1.19318 MHz or 0.119318 ticks per 100 ns.
193   //  TimerPeriod * 0.119318 = 8254 timer divisor. Using integer arithmetic
194   //  TimerCount = (TimerPeriod * 119318)/1000000.
195   //
196   //  Round up to next highest integer. This guarantees that the timer is
197   //  equal to or slightly longer than the requested time.
198   //  TimerCount = ((TimerPeriod * 119318) + 500000)/1000000
199   //
200   // Note that a TimerCount of 0 is equivalent to a count of 65,536
201   //
202   // Since TimerCount is limited to 16 bits for IA32, TimerPeriod is limited
203   // to 20 bits.
204   //
205   if (TimerPeriod == 0) {
206     //
207     // Disable timer interrupt for a TimerPeriod of 0
208     //
209     mLegacy8259->DisableIrq (mLegacy8259, Efi8259Irq0);
210   } else {
211 
212     //
213     // Convert TimerPeriod into 8254 counts
214     //
215     TimerCount = DivU64x32 (MultU64x32 (119318, (UINT32) TimerPeriod) + 500000, 1000000);
216 
217     //
218     // Check for overflow
219     //
220     if (TimerCount >= 65536) {
221       TimerCount = 0;
222       TimerPeriod = MAX_TIMER_TICK_DURATION;
223     }
224     //
225     // Program the 8254 timer with the new count value
226     //
227     SetPitCount ((UINT16) TimerCount);
228 
229     //
230     // Enable timer interrupt
231     //
232     mLegacy8259->EnableIrq (mLegacy8259, Efi8259Irq0, FALSE);
233   }
234   //
235   // Save the new timer period
236   //
237   mTimerPeriod = TimerPeriod;
238 
239   return EFI_SUCCESS;
240 }
241 
242 /**
243 
244   This function retrieves the period of timer interrupts in 100 ns units,
245   returns that value in TimerPeriod, and returns EFI_SUCCESS.  If TimerPeriod
246   is NULL, then EFI_INVALID_PARAMETER is returned.  If a TimerPeriod of 0 is
247   returned, then the timer is currently disabled.
248 
249 
250   @param This            The EFI_TIMER_ARCH_PROTOCOL instance.
251   @param TimerPeriod     A pointer to the timer period to retrieve in 100 ns units.  If
252                          0 is returned, then the timer is currently disabled.
253 
254   @retval EFI_SUCCESS            The timer period was returned in TimerPeriod.
255   @retval EFI_INVALID_PARAMETER  TimerPeriod is NULL.
256 
257 **/
258 EFI_STATUS
259 EFIAPI
TimerDriverGetTimerPeriod(IN EFI_TIMER_ARCH_PROTOCOL * This,OUT UINT64 * TimerPeriod)260 TimerDriverGetTimerPeriod (
261   IN EFI_TIMER_ARCH_PROTOCOL   *This,
262   OUT UINT64                   *TimerPeriod
263   )
264 {
265   if (TimerPeriod == NULL) {
266     return EFI_INVALID_PARAMETER;
267   }
268 
269   *TimerPeriod = mTimerPeriod;
270 
271   return EFI_SUCCESS;
272 }
273 
274 /**
275 
276   This function generates a soft timer interrupt. If the platform does not support soft
277   timer interrupts, then EFI_UNSUPPORTED is returned. Otherwise, EFI_SUCCESS is returned.
278   If a handler has been registered through the EFI_TIMER_ARCH_PROTOCOL.RegisterHandler()
279   service, then a soft timer interrupt will be generated. If the timer interrupt is
280   enabled when this service is called, then the registered handler will be invoked. The
281   registered handler should not be able to distinguish a hardware-generated timer
282   interrupt from a software-generated timer interrupt.
283 
284 
285   @param This              The EFI_TIMER_ARCH_PROTOCOL instance.
286 
287   @retval EFI_SUCCESS       The soft timer interrupt was generated.
288   @retval EFI_UNSUPPORTEDT  The platform does not support the generation of soft timer interrupts.
289 
290 **/
291 EFI_STATUS
292 EFIAPI
TimerDriverGenerateSoftInterrupt(IN EFI_TIMER_ARCH_PROTOCOL * This)293 TimerDriverGenerateSoftInterrupt (
294   IN EFI_TIMER_ARCH_PROTOCOL  *This
295   )
296 {
297   EFI_STATUS  Status;
298   UINT16      IRQMask;
299   EFI_TPL     OriginalTPL;
300 
301   //
302   // If the timer interrupt is enabled, then the registered handler will be invoked.
303   //
304   Status = mLegacy8259->GetMask (mLegacy8259, NULL, NULL, &IRQMask, NULL);
305   ASSERT_EFI_ERROR (Status);
306   if ((IRQMask & 0x1) == 0) {
307     //
308     // Invoke the registered handler
309     //
310     OriginalTPL = gBS->RaiseTPL (TPL_HIGH_LEVEL);
311 
312     if (mTimerNotifyFunction != NULL) {
313       //
314       // @bug : This does not handle missed timer interrupts
315       //
316       mTimerNotifyFunction (mTimerPeriod);
317     }
318 
319     gBS->RestoreTPL (OriginalTPL);
320   } else {
321     return EFI_UNSUPPORTED;
322   }
323 
324   return EFI_SUCCESS;
325 }
326 
327 /**
328   Initialize the Timer Architectural Protocol driver
329 
330   @param ImageHandle     ImageHandle of the loaded driver
331   @param SystemTable     Pointer to the System Table
332 
333   @retval EFI_SUCCESS            Timer Architectural Protocol created
334   @retval EFI_OUT_OF_RESOURCES   Not enough resources available to initialize driver.
335   @retval EFI_DEVICE_ERROR       A device error occured attempting to initialize the driver.
336 
337 **/
338 EFI_STATUS
339 EFIAPI
TimerDriverInitialize(IN EFI_HANDLE ImageHandle,IN EFI_SYSTEM_TABLE * SystemTable)340 TimerDriverInitialize (
341   IN EFI_HANDLE        ImageHandle,
342   IN EFI_SYSTEM_TABLE  *SystemTable
343   )
344 {
345   EFI_STATUS  Status;
346   UINT32      TimerVector;
347 
348   //
349   // Initialize the pointer to our notify function.
350   //
351   mTimerNotifyFunction = NULL;
352 
353   //
354   // Make sure the Timer Architectural Protocol is not already installed in the system
355   //
356   ASSERT_PROTOCOL_ALREADY_INSTALLED (NULL, &gEfiTimerArchProtocolGuid);
357 
358   //
359   // Find the CPU architectural protocol.
360   //
361   Status = gBS->LocateProtocol (&gEfiCpuArchProtocolGuid, NULL, (VOID **) &mCpu);
362   ASSERT_EFI_ERROR (Status);
363 
364   //
365   // Find the Legacy8259 protocol.
366   //
367   Status = gBS->LocateProtocol (&gEfiLegacy8259ProtocolGuid, NULL, (VOID **) &mLegacy8259);
368   ASSERT_EFI_ERROR (Status);
369 
370   //
371   // Force the timer to be disabled
372   //
373   Status = TimerDriverSetTimerPeriod (&mTimer, 0);
374   ASSERT_EFI_ERROR (Status);
375 
376   //
377   // Get the interrupt vector number corresponding to IRQ0 from the 8259 driver
378   //
379   TimerVector = 0;
380   Status      = mLegacy8259->GetVector (mLegacy8259, Efi8259Irq0, (UINT8 *) &TimerVector);
381   ASSERT_EFI_ERROR (Status);
382 
383   //
384   // Install interrupt handler for 8254 Timer #0 (ISA IRQ0)
385   //
386   Status = mCpu->RegisterInterruptHandler (mCpu, TimerVector, TimerInterruptHandler);
387   ASSERT_EFI_ERROR (Status);
388 
389   //
390   // Force the timer to be enabled at its default period
391   //
392   Status = TimerDriverSetTimerPeriod (&mTimer, DEFAULT_TIMER_TICK_DURATION);
393   ASSERT_EFI_ERROR (Status);
394 
395   //
396   // Install the Timer Architectural Protocol onto a new handle
397   //
398   Status = gBS->InstallMultipleProtocolInterfaces (
399                   &mTimerHandle,
400                   &gEfiTimerArchProtocolGuid, &mTimer,
401                   NULL
402                   );
403   ASSERT_EFI_ERROR (Status);
404 
405   return Status;
406 }
407 
408