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