Home | History | Annotate | Download | only in 8254TimerDxe
      1 /** @file
      2   Timer Architectural Protocol as defined in the DXE CIS
      3 
      4 Copyright (c) 2005 - 2016, 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
     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
     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
    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
    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
    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_UNSUPPORTED   The platform does not support the generation of soft timer interrupts.
    289 
    290 **/
    291 EFI_STATUS
    292 EFIAPI
    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
    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