Fix a bug reported by EDK940 "The main thread has not LeaveCriticalSection when be...
[people/mcb30/edk2.git] / edk2 / Nt32Pkg / TimerDxe / Timer.c
1 /*++\r
2 \r
3 Copyright (c) 2006, Intel Corporation                                                         \r
4 All rights reserved. This program and the accompanying materials                          \r
5 are licensed and made available under the terms and conditions of the BSD License         \r
6 which accompanies this distribution.  The full text of the license may be found at        \r
7 http://opensource.org/licenses/bsd-license.php                                            \r
8                                                                                           \r
9 THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,                     \r
10 WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.             \r
11 \r
12 Module Name:\r
13 \r
14   Timer.c\r
15 \r
16 Abstract:\r
17 \r
18   NT Emulation Timer Architectural Protocol Driver as defined in DXE CIS\r
19 \r
20   This Timer module uses an NT Thread to simulate the timer-tick driven\r
21   timer service.  In the future, the Thread creation should possibly be \r
22   abstracted by the CPU architectural protocol\r
23 \r
24 --*/\r
25 \r
26 #include "Timer.h"\r
27 \r
28 //\r
29 // Pointer to the CPU Architectural Protocol instance\r
30 //\r
31 EFI_CPU_ARCH_PROTOCOL   *mCpu;\r
32 \r
33 //\r
34 // The Timer Architectural Protocol that this driver produces\r
35 //\r
36 EFI_TIMER_ARCH_PROTOCOL mTimer = {\r
37   WinNtTimerDriverRegisterHandler,\r
38   WinNtTimerDriverSetTimerPeriod,\r
39   WinNtTimerDriverGetTimerPeriod,\r
40   WinNtTimerDriverGenerateSoftInterrupt\r
41 };\r
42 \r
43 //\r
44 // Define a global that we can use to shut down the NT timer thread when\r
45 // the timer is canceled.\r
46 //\r
47 BOOLEAN                 mCancelTimerThread = FALSE;\r
48 \r
49 //\r
50 // The notification function to call on every timer interrupt\r
51 //\r
52 EFI_TIMER_NOTIFY        mTimerNotifyFunction = NULL;\r
53 \r
54 //\r
55 // The current period of the timer interrupt\r
56 //\r
57 UINT64                  mTimerPeriod;\r
58 \r
59 //\r
60 // The thread handle for this driver\r
61 //\r
62 HANDLE                  mNtMainThreadHandle;\r
63 \r
64 //\r
65 // The timer value from the last timer interrupt\r
66 //\r
67 UINT32                  mNtLastTick;\r
68 \r
69 //\r
70 // Critical section used to update varibles shared between the main thread and\r
71 // the timer interrupt thread.\r
72 //\r
73 CRITICAL_SECTION        mNtCriticalSection;\r
74 \r
75 //\r
76 // Worker Functions\r
77 //\r
78 UINT                    mMMTimerThreadID = 0;\r
79 \r
80 VOID\r
81 CALLBACK\r
82 MMTimerThread (\r
83   UINT  wTimerID,\r
84   UINT  msg,\r
85   DWORD dwUser,\r
86   DWORD dw1,\r
87   DWORD dw2\r
88   )\r
89 /*++\r
90 \r
91 Routine Description:\r
92 \r
93   TODO: Add function description\r
94 \r
95 Arguments:\r
96 \r
97   wTimerID  - TODO: add argument description\r
98   msg       - TODO: add argument description\r
99   dwUser    - TODO: add argument description\r
100   dw1       - TODO: add argument description\r
101   dw2       - TODO: add argument description\r
102 \r
103 Returns:\r
104 \r
105   TODO: add return values\r
106 \r
107 --*/\r
108 {\r
109   EFI_TPL           OriginalTPL;\r
110   UINT32            CurrentTick;\r
111   UINT32            Delta;\r
112   EFI_TIMER_NOTIFY  CallbackFunction;\r
113   BOOLEAN           InterruptState;\r
114 \r
115   if (!mCancelTimerThread) {\r
116   \r
117     //\r
118     // Suspend the main thread until we are done.\r
119     // Enter the critical section before suspending\r
120     // and leave the critical section after resuming\r
121     // to avoid deadlock between main and timer thread.\r
122     //\r
123     gWinNt->EnterCriticalSection (&mNtCriticalSection);\r
124     gWinNt->SuspendThread (mNtMainThreadHandle);\r
125 \r
126     //\r
127     // If the timer thread is being canceled, then bail immediately.\r
128     // We check again here because there's a small window of time from when\r
129     // this thread was kicked off and when we suspended the main thread above.\r
130     //\r
131     if (mCancelTimerThread) {\r
132       gWinNt->ResumeThread (mNtMainThreadHandle);\r
133       gWinNt->LeaveCriticalSection (&mNtCriticalSection);\r
134       gWinNt->timeKillEvent (wTimerID);\r
135       mMMTimerThreadID = 0;\r
136       return ;\r
137     }\r
138 \r
139     mCpu->GetInterruptState (mCpu, &InterruptState);\r
140     while (!InterruptState) {\r
141       //\r
142       //  Resume the main thread\r
143       //\r
144       gWinNt->ResumeThread (mNtMainThreadHandle);\r
145       gWinNt->LeaveCriticalSection (&mNtCriticalSection);\r
146 \r
147       //\r
148       //  Wait for interrupts to be enabled.\r
149       //\r
150       mCpu->GetInterruptState (mCpu, &InterruptState);\r
151       while (!InterruptState) {\r
152         gWinNt->Sleep (1);\r
153         mCpu->GetInterruptState (mCpu, &InterruptState);\r
154       }\r
155        \r
156       //\r
157       //  Suspend the main thread until we are done\r
158       //\r
159       gWinNt->EnterCriticalSection (&mNtCriticalSection);\r
160       gWinNt->SuspendThread (mNtMainThreadHandle);\r
161       mCpu->GetInterruptState (mCpu, &InterruptState);\r
162     }\r
163 \r
164     //\r
165     //  Get the current system tick\r
166     //\r
167     CurrentTick = gWinNt->GetTickCount ();\r
168     Delta       = CurrentTick - mNtLastTick;\r
169     mNtLastTick = CurrentTick;\r
170 \r
171     //\r
172     //  If delay was more then 1 second, ignore it (probably debugging case)\r
173     //\r
174     if (Delta < 1000) {\r
175 \r
176       OriginalTPL = gBS->RaiseTPL (TPL_HIGH_LEVEL);\r
177 \r
178       //\r
179       //  Inform the firmware of an "timer interrupt".  The time\r
180       //  expired since the last call is 10,000 times the number\r
181       //  of ms.  (or 100ns units)\r
182       //\r
183       CallbackFunction = mTimerNotifyFunction;\r
184 \r
185       //\r
186       // Only invoke the callback function if a Non-NULL handler has been\r
187       // registered. Assume all other handlers are legal.\r
188       //\r
189       if (CallbackFunction != NULL) {\r
190         CallbackFunction ((UINT64) (Delta * 10000));\r
191       }\r
192 \r
193       gBS->RestoreTPL (OriginalTPL);\r
194 \r
195     }\r
196 \r
197     //\r
198     //  Resume the main thread\r
199     //\r
200     gWinNt->ResumeThread (mNtMainThreadHandle);\r
201     gWinNt->LeaveCriticalSection (&mNtCriticalSection);\r
202   } else {\r
203     gWinNt->timeKillEvent (wTimerID);\r
204     mMMTimerThreadID = 0;\r
205   }\r
206 \r
207 }\r
208 \r
209 UINT\r
210 CreateNtTimer (\r
211   VOID\r
212   )\r
213 /*++\r
214 \r
215 Routine Description:\r
216 \r
217    It is used to emulate a platform \r
218   timer-driver interrupt handler.  \r
219 \r
220 Returns:\r
221 \r
222   Timer ID\r
223 \r
224 --*/\r
225 // TODO: function comment is missing 'Arguments:'\r
226 {\r
227   UINT32  SleepCount;\r
228 \r
229   //\r
230   //  Set our thread priority higher than the "main" thread.\r
231   //\r
232   gWinNt->SetThreadPriority (\r
233             gWinNt->GetCurrentThread (),\r
234             THREAD_PRIORITY_HIGHEST\r
235             );\r
236 \r
237   //\r
238   //  Calc the appropriate interval\r
239   //\r
240   gWinNt->EnterCriticalSection (&mNtCriticalSection);\r
241   SleepCount = (UINT32) (mTimerPeriod + 5000) / 10000;\r
242   gWinNt->LeaveCriticalSection (&mNtCriticalSection);\r
243 \r
244   return gWinNt->timeSetEvent (\r
245                   SleepCount,\r
246                   0,\r
247                   MMTimerThread,\r
248                   (DWORD_PTR) NULL,\r
249                   TIME_PERIODIC | TIME_KILL_SYNCHRONOUS | TIME_CALLBACK_FUNCTION\r
250                   );\r
251 \r
252 }\r
253 \r
254 EFI_STATUS\r
255 EFIAPI\r
256 WinNtTimerDriverRegisterHandler (\r
257   IN EFI_TIMER_ARCH_PROTOCOL           *This,\r
258   IN EFI_TIMER_NOTIFY                  NotifyFunction\r
259   )\r
260 /*++\r
261 \r
262 Routine Description:\r
263 \r
264   This function registers the handler NotifyFunction so it is called every time \r
265   the timer interrupt fires.  It also passes the amount of time since the last \r
266   handler call to the NotifyFunction.  If NotifyFunction is NULL, then the \r
267   handler is unregistered.  If the handler is registered, then EFI_SUCCESS is \r
268   returned.  If the CPU does not support registering a timer interrupt handler, \r
269   then EFI_UNSUPPORTED is returned.  If an attempt is made to register a handler \r
270   when a handler is already registered, then EFI_ALREADY_STARTED is returned.  \r
271   If an attempt is made to unregister a handler when a handler is not registered, \r
272   then EFI_INVALID_PARAMETER is returned.  If an error occurs attempting to \r
273   register the NotifyFunction with the timer interrupt, then EFI_DEVICE_ERROR \r
274   is returned.\r
275 \r
276 Arguments:\r
277 \r
278   This           - The EFI_TIMER_ARCH_PROTOCOL instance.\r
279 \r
280   NotifyFunction - The function to call when a timer interrupt fires.  This \r
281                    function executes at TPL_HIGH_LEVEL.  The DXE Core will \r
282                    register a handler for the timer interrupt, so it can know \r
283                    how much time has passed.  This information is used to \r
284                    signal timer based events.  NULL will unregister the handler.\r
285 \r
286 Returns: \r
287 \r
288   EFI_SUCCESS           - The timer handler was registered.\r
289 \r
290   EFI_UNSUPPORTED       - The platform does not support timer interrupts.\r
291 \r
292   EFI_ALREADY_STARTED   - NotifyFunction is not NULL, and a handler is already \r
293                           registered.\r
294 \r
295   EFI_INVALID_PARAMETER - NotifyFunction is NULL, and a handler was not \r
296                           previously registered.\r
297 \r
298   EFI_DEVICE_ERROR      - The timer handler could not be registered.\r
299 \r
300 --*/\r
301 {\r
302   //\r
303   // Check for invalid parameters\r
304   //\r
305   if (NotifyFunction == NULL && mTimerNotifyFunction == NULL) {\r
306     return EFI_INVALID_PARAMETER;\r
307   }\r
308 \r
309   if (NotifyFunction != NULL && mTimerNotifyFunction != NULL) {\r
310     return EFI_ALREADY_STARTED;\r
311   }\r
312 \r
313   //\r
314   // Use Critical Section to update the notification function that is\r
315   // used from the timer interrupt thread.\r
316   //\r
317   gWinNt->EnterCriticalSection (&mNtCriticalSection);\r
318 \r
319   mTimerNotifyFunction = NotifyFunction;\r
320 \r
321   gWinNt->LeaveCriticalSection (&mNtCriticalSection);\r
322 \r
323   return EFI_SUCCESS;\r
324 }\r
325 \r
326 EFI_STATUS\r
327 EFIAPI\r
328 WinNtTimerDriverSetTimerPeriod (\r
329   IN EFI_TIMER_ARCH_PROTOCOL  *This,\r
330   IN UINT64                   TimerPeriod\r
331   )\r
332 /*++\r
333 \r
334 Routine Description:\r
335 \r
336   This function adjusts the period of timer interrupts to the value specified \r
337   by TimerPeriod.  If the timer period is updated, then the selected timer \r
338   period is stored in EFI_TIMER.TimerPeriod, and EFI_SUCCESS is returned.  If \r
339   the timer hardware is not programmable, then EFI_UNSUPPORTED is returned.  \r
340   If an error occurs while attempting to update the timer period, then the \r
341   timer hardware will be put back in its state prior to this call, and \r
342   EFI_DEVICE_ERROR is returned.  If TimerPeriod is 0, then the timer interrupt \r
343   is disabled.  This is not the same as disabling the CPU's interrupts.  \r
344   Instead, it must either turn off the timer hardware, or it must adjust the \r
345   interrupt controller so that a CPU interrupt is not generated when the timer \r
346   interrupt fires. \r
347 \r
348 Arguments:\r
349 \r
350   This        - The EFI_TIMER_ARCH_PROTOCOL instance.\r
351 \r
352   TimerPeriod - The rate to program the timer interrupt in 100 nS units.  If \r
353                 the timer hardware is not programmable, then EFI_UNSUPPORTED is \r
354                 returned.  If the timer is programmable, then the timer period \r
355                 will be rounded up to the nearest timer period that is supported \r
356                 by the timer hardware.  If TimerPeriod is set to 0, then the \r
357                 timer interrupts will be disabled.\r
358 \r
359 Returns: \r
360 \r
361   EFI_SUCCESS      - The timer period was changed.\r
362 \r
363   EFI_UNSUPPORTED  - The platform cannot change the period of the timer interrupt.\r
364 \r
365   EFI_DEVICE_ERROR - The timer period could not be changed due to a device error.\r
366 \r
367 --*/\r
368 {\r
369 \r
370   //\r
371   // If TimerPeriod is 0, then the timer thread should be canceled\r
372   //\r
373   if (TimerPeriod == 0) {\r
374     //\r
375     // Cancel the timer thread\r
376     //\r
377     gWinNt->EnterCriticalSection (&mNtCriticalSection);\r
378 \r
379     mCancelTimerThread = TRUE;\r
380 \r
381     gWinNt->LeaveCriticalSection (&mNtCriticalSection);\r
382 \r
383     //\r
384     // Wait for the timer thread to exit\r
385     //\r
386 \r
387     if (mMMTimerThreadID) {\r
388       gWinNt->timeKillEvent (mMMTimerThreadID);\r
389     }\r
390 \r
391     mMMTimerThreadID = 0;\r
392 \r
393     //\r
394     // Update the timer period\r
395     //\r
396     gWinNt->EnterCriticalSection (&mNtCriticalSection);\r
397 \r
398     mTimerPeriod = TimerPeriod;\r
399 \r
400     gWinNt->LeaveCriticalSection (&mNtCriticalSection);\r
401 \r
402     //\r
403     // NULL out the thread handle so it will be re-created if the timer is enabled again\r
404     //\r
405 \r
406   } else if ((TimerPeriod > TIMER_MINIMUM_VALUE) && (TimerPeriod < TIMER_MAXIMUM_VALUE)) {\r
407     //\r
408     // If the TimerPeriod is valid, then create and/or adjust the period of the timer thread\r
409     //\r
410     gWinNt->EnterCriticalSection (&mNtCriticalSection);\r
411 \r
412     mTimerPeriod        = TimerPeriod;\r
413 \r
414     mCancelTimerThread  = FALSE;\r
415 \r
416     gWinNt->LeaveCriticalSection (&mNtCriticalSection);\r
417 \r
418     //\r
419     //  Get the starting tick location if we are just starting the timer thread\r
420     //\r
421     mNtLastTick = gWinNt->GetTickCount ();\r
422 \r
423     if (mMMTimerThreadID) {\r
424       gWinNt->timeKillEvent (mMMTimerThreadID);\r
425     }\r
426 \r
427     mMMTimerThreadID  = 0;\r
428 \r
429     mMMTimerThreadID  = CreateNtTimer ();\r
430 \r
431   }\r
432 \r
433   return EFI_SUCCESS;\r
434 }\r
435 \r
436 EFI_STATUS\r
437 EFIAPI\r
438 WinNtTimerDriverGetTimerPeriod (\r
439   IN EFI_TIMER_ARCH_PROTOCOL            *This,\r
440   OUT UINT64                            *TimerPeriod\r
441   )\r
442 /*++\r
443 \r
444 Routine Description:\r
445 \r
446   This function retrieves the period of timer interrupts in 100 ns units, \r
447   returns that value in TimerPeriod, and returns EFI_SUCCESS.  If TimerPeriod \r
448   is NULL, then EFI_INVALID_PARAMETER is returned.  If a TimerPeriod of 0 is \r
449   returned, then the timer is currently disabled.\r
450 \r
451 Arguments:\r
452 \r
453   This        - The EFI_TIMER_ARCH_PROTOCOL instance.\r
454 \r
455   TimerPeriod - A pointer to the timer period to retrieve in 100 ns units.  If \r
456                 0 is returned, then the timer is currently disabled.\r
457 \r
458 Returns: \r
459 \r
460   EFI_SUCCESS           - The timer period was returned in TimerPeriod.\r
461 \r
462   EFI_INVALID_PARAMETER - TimerPeriod is NULL.\r
463 \r
464 --*/\r
465 {\r
466   if (TimerPeriod == NULL) {\r
467     return EFI_INVALID_PARAMETER;\r
468   }\r
469 \r
470   *TimerPeriod = mTimerPeriod;\r
471 \r
472   return EFI_SUCCESS;\r
473 }\r
474 \r
475 EFI_STATUS\r
476 EFIAPI\r
477 WinNtTimerDriverGenerateSoftInterrupt (\r
478   IN EFI_TIMER_ARCH_PROTOCOL  *This\r
479   )\r
480 /*++\r
481 \r
482 Routine Description:\r
483 \r
484   This function generates a soft timer interrupt. If the platform does not support soft \r
485   timer interrupts, then EFI_UNSUPPORTED is returned. Otherwise, EFI_SUCCESS is returned. \r
486   If a handler has been registered through the EFI_TIMER_ARCH_PROTOCOL.RegisterHandler() \r
487   service, then a soft timer interrupt will be generated. If the timer interrupt is \r
488   enabled when this service is called, then the registered handler will be invoked. The \r
489   registered handler should not be able to distinguish a hardware-generated timer \r
490   interrupt from a software-generated timer interrupt.\r
491 \r
492 Arguments:\r
493 \r
494   This  -  The EFI_TIMER_ARCH_PROTOCOL instance.\r
495 \r
496 Returns: \r
497 \r
498   EFI_SUCCESS       - The soft timer interrupt was generated.\r
499 \r
500   EFI_UNSUPPORTEDT  - The platform does not support the generation of soft timer interrupts.\r
501 \r
502 --*/\r
503 {\r
504   return EFI_UNSUPPORTED;\r
505 }\r
506 \r
507 \r
508 EFI_STATUS\r
509 EFIAPI\r
510 WinNtTimerDriverInitialize (\r
511   IN EFI_HANDLE        ImageHandle,\r
512   IN EFI_SYSTEM_TABLE  *SystemTable\r
513   )\r
514 /*++\r
515 \r
516 Routine Description:\r
517 \r
518   Initialize the Timer Architectural Protocol driver\r
519 \r
520 Arguments:\r
521 \r
522   ImageHandle - ImageHandle of the loaded driver\r
523 \r
524   SystemTable - Pointer to the System Table\r
525 \r
526 Returns:\r
527 \r
528   EFI_SUCCESS           - Timer Architectural Protocol created\r
529 \r
530   EFI_OUT_OF_RESOURCES  - Not enough resources available to initialize driver.\r
531   \r
532   EFI_DEVICE_ERROR      - A device error occured attempting to initialize the driver.\r
533 \r
534 --*/\r
535 {\r
536   EFI_STATUS  Status;\r
537   UINTN       Result;\r
538   EFI_HANDLE  Handle;\r
539 \r
540   //\r
541   // Make sure the Timer Architectural Protocol is not already installed in the system\r
542   //\r
543   ASSERT_PROTOCOL_ALREADY_INSTALLED (NULL, &gEfiTimerArchProtocolGuid);\r
544 \r
545   //\r
546   // Get the CPU Architectural Protocol instance\r
547   //\r
548   Status = gBS->LocateProtocol (&gEfiCpuArchProtocolGuid, NULL, &mCpu);\r
549   ASSERT_EFI_ERROR (Status);\r
550 \r
551   //\r
552   //  Get our handle so the timer tick thread can suspend\r
553   //\r
554   Result = gWinNt->DuplicateHandle (\r
555                     gWinNt->GetCurrentProcess (),\r
556                     gWinNt->GetCurrentThread (),\r
557                     gWinNt->GetCurrentProcess (),\r
558                     &mNtMainThreadHandle,\r
559                     0,\r
560                     FALSE,\r
561                     DUPLICATE_SAME_ACCESS\r
562                     );\r
563   if (Result == 0) {\r
564     return EFI_DEVICE_ERROR;\r
565   }\r
566 \r
567   //\r
568   // Initialize Critical Section used to update variables shared between the main\r
569   // thread and the timer interrupt thread.\r
570   //\r
571   gWinNt->InitializeCriticalSection (&mNtCriticalSection);\r
572 \r
573   //\r
574   // Start the timer thread at the default timer period\r
575   //\r
576   Status = mTimer.SetTimerPeriod (&mTimer, DEFAULT_TIMER_TICK_DURATION);\r
577   if (EFI_ERROR (Status)) {\r
578     gWinNt->DeleteCriticalSection (&mNtCriticalSection);\r
579     return Status;\r
580   }\r
581 \r
582   //\r
583   // Install the Timer Architectural Protocol onto a new handle\r
584   //\r
585   Handle = NULL;\r
586   Status = gBS->InstallProtocolInterface (\r
587                   &Handle,\r
588                   &gEfiTimerArchProtocolGuid,\r
589                   EFI_NATIVE_INTERFACE,\r
590                   &mTimer\r
591                   );\r
592   if (EFI_ERROR (Status)) {\r
593     //\r
594     // Cancel the timer\r
595     //\r
596     mTimer.SetTimerPeriod (&mTimer, 0);\r
597     gWinNt->DeleteCriticalSection (&mNtCriticalSection);\r
598     return Status;\r
599   }\r
600 \r
601   return EFI_SUCCESS;\r
602 }\r