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