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