Step to UEFI (135)EFI_CPU_ARCH_PROTOCOL 注册的 INTx

前面提到的 EFI_CPU_ARCH_PROTOCOL ,这次试试这个Protocol RegisterInterrupt,注册一个我们自己的中断试试。

rd3

为了完成这个目标,我们设计下面的方案:
1. 编写一个驱动,在这个驱动里面注册我们自己的 Interrupt,我使用的是 0x41 作为中断向量(经过检查,实际机器上使用了0x40作为给HPET Timer的Interrupt,这里我们选择一个不冲突的即可)。方便起见,选择使用之前我们设计的 Print9 Protocol 的代码,这个代码会在系统中注册我们自己定义的 Protocol,然后这个 Protocol 中留出来一个输出变量(Var2)的函数,以便调用。我们的InterruptHandler也在这个文件中,内容很简单,就是将 Var2 加一;
2. 编写一个 Application 来在系统中查找上面驱动注册的Protocol,找到之后调用输出变量的函数,这样我们可以知道这个函数是否有变化;
3. 最后再编写一个 Application 用来产生 int 0x41。
首先是第一个的代码,这个需要放置在 MdeModule 中编译而不是我们经常用的 AppPkg中,插入在 MdeModulePkg.dsc 文件中

  MdeModulePkg/Library/DxeCapsuleLibFmp/DxeCapsuleLib.inf
  MdeModulePkg/Library/DxeCapsuleLibFmp/DxeRuntimeCapsuleLib.inf

#LabZDebug_Start
  MdeModulePkg/PrintDriver3/PrintDriver3.inf
#LabZDebug_End

 [Components.IA32, Components.X64, Components.IPF, Components.AARCH64]
  MdeModulePkg/Universal/Network/UefiPxeBcDxe/UefiPxeBcDxe.inf
  MdeModulePkg/Universal/DebugSupportDxe/DebugSupportDxe.inf

 

代码如下:

/** @file
  This driver produces Print9 protocol layered on top of the PrintLib from the MdePkg.

Copyright (c) 2009, Intel Corporation. All rights reserved.<BR>
This program and the accompanying materials
are licensed and made available under the terms and conditions of the BSD License
which accompanies this distribution.  The full text of the license may be found at
http://opensource.org/licenses/bsd-license.php

THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.

**/

#include <PiDxe.h>
#include <Library/UefiLib.h>

#include <Library/PrintLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/DebugLib.h>
#include <Library/UefiDriverEntryPoint.h>
#include <Library/MemoryAllocationLib.h>
#include "Print9.h"

#include <Protocol/Cpu.h>
#include <Library/CpuLib.h>

extern EFI_SYSTEM_TABLE         *gST;

EFI_GUID gEfiPrint9ProtocolGuid =
                { 0xf05976ef, 0x83f1, 0x4f3d, 
                  { 0x86, 0x19, 0xf7, 0x59,0x5d, 0x41, 0xe5, 0x61 } };

EFI_GUID gEfiCpuArchProtocolGuid = 
                { 0x26BACCB1, 0x6F42, 0x11D4, 
                  { 0xBC, 0xE7, 0x00, 0x80, 0xC7, 0x3C, 0x88, 0x81 }};

EFI_PRINT9_PRIVATE_DATA         *Image;
EFI_HANDLE  mPrintThunkHandle   = NULL;

//Copied from \MdeModulePkg\Library\DxePrintLibPrint2Protocol\PrintLib.c
UINTN
EFIAPI
MyPrint ()
{
  CHAR16  *Buffer=L"1 2 3 4 5 6 7 8 9 0 A B C E D F ";
  
  UnicodeSPrint(Buffer,16,L"%d\r\n",Image->Var2);
  gST->ConOut->OutputString(gST->ConOut,Buffer); 
  
  return 0;
}

VOID
EFIAPI
MyInterruptHandler (
  IN EFI_EXCEPTION_TYPE   InterruptType,
  IN EFI_SYSTEM_CONTEXT   SystemContext
  )
{
       Image->Var2++; 
}  
/**
  The user Entry Point for Print module.

  This is the entry point for Print DXE Driver. It installs the Print2 Protocol.

  @param[in] ImageHandle    The firmware allocated handle for the EFI image.
  @param[in] SystemTable    A pointer to the EFI System Table.

  @retval EFI_SUCCESS       The entry point is executed successfully.
  @retval Others            Some error occurs when executing this entry point.

**/
EFI_STATUS
EFIAPI
PrintEntryPoint (
  IN EFI_HANDLE           ImageHandle,
  IN EFI_SYSTEM_TABLE     *SystemTable
  )
{
        EFI_STATUS              Status;
        EFI_CPU_ARCH_PROTOCOL  *Cpu;
        
        //
        // Allocate a new image structure
        //
        Image = AllocateZeroPool (sizeof(EFI_PRINT9_PRIVATE_DATA));
        if (Image == NULL) {
                Status = EFI_OUT_OF_RESOURCES;
                ASSERT_EFI_ERROR (Status);
        }

        Image->Signature         = PRINT9_PRIVATE_DATA_SIGNATURE;
  
        Image->PRINT9.UnicodeBSPrint=UnicodeBSPrint;
        Image->PRINT9.UnicodeSPrint=UnicodeSPrint;
        Image->PRINT9.UnicodeBSPrintAsciiFormat=UnicodeBSPrintAsciiFormat;
        Image->PRINT9.UnicodeSPrintAsciiFormat=MyPrint;
        //Image->PRINT9.UnicodeValueToString=UnicodeValueToString;
        Image->PRINT9.AsciiBSPrint=AsciiBSPrint;
        Image->PRINT9.AsciiSPrint=AsciiSPrint;        
        Image->PRINT9.AsciiBSPrintUnicodeFormat=AsciiBSPrintUnicodeFormat;
        Image->PRINT9.AsciiSPrintUnicodeFormat=AsciiSPrintUnicodeFormat;
        //Image->PRINT9.AsciiValueToString=AsciiValueToString;

        Image->Var2=1984;
        
        Status = gBS->InstallMultipleProtocolInterfaces (
                  &mPrintThunkHandle,
                  &gEfiPrint9ProtocolGuid, 
                  &Image->PRINT9,
                  NULL
                );
        ASSERT_EFI_ERROR (Status);

        //
        // Locate the Cpu Arch Protocol.
        //
        Status = gBS->LocateProtocol (&gEfiCpuArchProtocolGuid, NULL, &Cpu);
        ASSERT_EFI_ERROR (Status);

        Status = Cpu->RegisterInterruptHandler (Cpu, 0x41, MyInterruptHandler);
        ASSERT_EFI_ERROR (Status);
        
  return Status;
}

 

上述代码有两个需要特别注意的地方:
1. 之前的 Print9代码是在 UDK2014中编译通过的,但是在 UDK2017中无法编译通过,根本原因是 UDK2017中因为安全原因删除了 UnicodeValueToString 和AsciiValueToString两个函数,对我们来说,在代码中注视掉这两个函数不使用即可;
2. MyPrint 是我们输出函数,他会输出 Image->Var2 的值;
3. MyInterruptHandler 是我们的中断函数,里面只是简单的对 Image->Var2 加一。

第二个代码,在系统中查找我们自定义的 Print9Protocol,相对来说简单多了:

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>

#include "Print9.h"

EFI_GUID gEfiPrint9ProtocolGuid =
                { 0xf05976ef, 0x83f1, 0x4f3d, 
                  { 0x86, 0x19, 0xf7, 0x59, 
                    0x5d, 0x41, 0xe5, 0x61 } };

extern EFI_BOOT_SERVICES         *gBS;

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
        EFI_PRINT9_PROTOCOL     *Print9Protocol;
        EFI_STATUS              Status;

        // Search for the Print9 Protocol
        Status = gBS->LocateProtocol(
                        &gEfiPrint9ProtocolGuid,
                        NULL,
                        (VOID **)&Print9Protocol
                );
        if (EFI_ERROR(Status)) {
                Print(L"Can't find Print9Protocol.\n");
                return EFI_SUCCESS;
        }
        
        Print(L"Find Print9Protocol.\n"); 
        Print9Protocol->UnicodeSPrintAsciiFormat();
        Print(L"\n"); 
        return EFI_SUCCESS;
}

 

第三个代码,发出 Int 0x41 中断。起初我打算使用 int al 这样的指令,后来查了一下手册,原来 int 后面只能接立即数,于是直接写成 int 0x41 。因为,Vistual Studio 的 X64无法使用内嵌汇编,我们只好单独写一个 asm 出来。

intx1

IntX.c:

/** @file
  Simple interrupt test.

Copyright (c) 2013, Intel Corporation. All rights reserved.<BR>
This program and the accompanying materials
are licensed and made available under the terms and conditions of the BSD License
which accompanies this distribution.  The full text of the license may be found at
http://opensource.org/licenses/bsd-license.php

THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.

**/
#include <Uefi.h>
#include <Library/BaseLib.h>
#include <Library/UefiLib.h>

#include "IntX.h"

/**
  The user Entry Point for Application. The user code starts with this function
  as the real entry point for the application.

  @param[in] ImageHandle    The firmware allocated handle for the EFI image.
  @param[in] SystemTable    A pointer to the EFI System Table.

  @retval EFI_SUCCESS       The entry point is executed successfully.
  @retval other             Some error occurs when executing this entry point.

**/
EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
        Print (L"Generate a Interrupt\n");
        
        SimpleInterrupt();
        
        return EFI_SUCCESS;
}

 

IntDemo.inf:

## @file
#   A simple, basic, application showing how the Hello application could be
#   built using the "Standard C Libraries" from StdLib.
#
#  Copyright (c) 2010 - 2011, Intel Corporation. All rights reserved.<BR>
#  This program and the accompanying materials
#  are licensed and made available under the terms and conditions of the BSD License
#  which accompanies this distribution. The full text of the license may be found at
#  http://opensource.org/licenses/bsd-license.
#
#  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
#  WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
##

[Defines]
  INF_VERSION                    = 0x00010006
  BASE_NAME                      = IntDemo
  FILE_GUID                      = 4ea97c01-7491-4dfd-0090-747010f3ce5f
  MODULE_TYPE                    = UEFI_APPLICATION
  VERSION_STRING                 = 0.1
  ENTRY_POINT                    = UefiMain

#   
#  VALID_ARCHITECTURES           = X64
#

[Sources.common]
  IntX.c
  IntX.h

[Sources.IA32]

[Sources.X64]
  X64/AsmInt.asm

[Packages]
  MdePkg/MdePkg.dec
  ShellPkg/ShellPkg.dec 

[LibraryClasses]
  UefiApplicationEntryPoint
  UefiLib

 

用来定义 Int X 的源代码 \x64\AsmInt.asm

;------------------------------------------------------------------------------
;
; Copyright (c) 2013, Intel Corporation. All rights reserved.<BR>
; This program and the accompanying materials
; are licensed and made available under the terms and conditions of the BSD License
; which accompanies this distribution.  The full text of the license may be found at
; http://opensource.org/licenses/bsd-license.php.
;
; THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
; WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
;
; Module Name:
;
;   AsmRdRand.Asm
;
; Abstract:
;
;   Generate an Interrupt by INT x
;
; Notes:
;
;   Visual Studio coding practices do not use inline asm since multiple compilers and 
;   architectures are supported assembler not recognizing rdrand instruction so using DB's.
;
;------------------------------------------------------------------------------

    .code
 
;------------------------------------------------------------------------------
;  Generates a interrupt by Int(CD).
;------------------------------------------------------------------------------
SimpleInterrupt  PROC
    int         0x41
    ret
SimpleInterrupt ENDP

    END

 

最终运行结果,可以看到每次运行 IntDemo之后,会有中断触发,数值不断变大。
intx2
完整的代码和编译后的 efi 文件下载(我只在X64 上测试过,NT32模拟环境不支持)
IntTest

Step to UEFI (134)多一个时间中断

前面的实验已经证明:UEFI 架构本身只有一个中断,那就是时间中断,在实体机上测试显示使用的是 HPET 的时间中断。那么我们是否可以再给UEFI多加一个中断?答案是肯定的,只要硬件上有支持,我们就可以多加入一个中断。这次我们实验的就是 8254 上面的中断。8254 根据我的理解,就是一个能够发出脉冲的时间芯片。具体脉冲发出来了,还需要有一个中断芯片来管理这些中断,在 Leagcy 的X86中,这个工作是 8259 来完成的,下面就是各种微机原理都会展示的图片。因为中断比较多,所以需要2片来级联进行扩展。对于我们的想法来说,只要知道8254 是接在第一个(主,Master) 8259的Pin0 上(IRQ0)即可。
mi1

一直困扰我的问题是 IRQx对应在IDT 中的哪一个,直到最近看到了 EFI_LEGACY_8259_PROTOCOL 中的 GetVector 函数【参考1】:
mi2
mi3

这个函数的作用就是返回当前IRQ 的Vector,也是就是IDT中的入口号。
了解了上面一些,就可以动手写程序了。使用我们之前的 PrintDriver3 的架构,使用 8259 的 Protocol来完成 8254 的初始化,设定 Interrupt 的Handler 是 MyInterruptHander,在这个函数中对 Var2 自增,最后使用之前的 PDT3 Application 打印 Var2 的数值。运行起来我们的Driver 之后,8254会不断发送中断出来,直观的说就是代码会运行到MyInterruptHander中,我们在这个函数中对于做标记的变量自增,最后用另外的 Protocol 找到这个变量并且打印出来。
完整代码如下:

/** @file
  This driver produces Print9 protocol layered on top of the PrintLib from the MdePkg.

Copyright (c) 2009, Intel Corporation. All rights reserved.<BR>
This program and the accompanying materials
are licensed and made available under the terms and conditions of the BSD License
which accompanies this distribution.  The full text of the license may be found at
http://opensource.org/licenses/bsd-license.php

THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.

**/

#include <PiDxe.h>
#include <Library/UefiLib.h>

#include <Library/PrintLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/DebugLib.h>
#include <Library/UefiDriverEntryPoint.h>
#include <Library/MemoryAllocationLib.h>
#include "Print9.h"

#include <Library/IoLib.h>
#include <Protocol/Cpu.h>
#include <Library/CpuLib.h>
#include "Legacy8259.h"

extern EFI_SYSTEM_TABLE         *gST;

EFI_GUID gEfiPrint9ProtocolGuid =
                { 0xf05976ef, 0x83f1, 0x4f3d, 
                  { 0x86, 0x19, 0xf7, 0x59,0x5d, 0x41, 0xe5, 0x61 } };

EFI_GUID gEfiCpuArchProtocolGuid = 
                { 0x26BACCB1, 0x6F42, 0x11D4, 
                  { 0xBC, 0xE7, 0x00, 0x80, 0xC7, 0x3C, 0x88, 0x81 }};

// Include/Protocol/Legacy8259.h
EFI_GUID gEfiLegacy8259ProtocolGuid     = 
                { 0x38321dba, 0x4fe0, 0x4e17, 
                  { 0x8a, 0xec, 0x41, 0x30, 0x55, 0xea, 0xed, 0xc1 }};

//\PcAtChipsetPkg\8254TimerDxe\Timer.h
//
// The maximum tick duration for 8254 timer
//
#define MAX_TIMER_TICK_DURATION     549254
//
// The default timer tick duration is set to 10 ms = 100000 100 ns units
//
#define DEFAULT_TIMER_TICK_DURATION 100000
#define TIMER_CONTROL_PORT          0x43
#define TIMER0_COUNT_PORT           0x40

//
// The current period of the timer interrupt
//
volatile UINT64           mTimerPeriod = 0;
                  
EFI_PRINT9_PRIVATE_DATA         *Image;
EFI_HANDLE  mPrintThunkHandle   = NULL;
//
// Pointer to the Legacy 8259 Protocol instance
//
EFI_LEGACY_8259_PROTOCOL  *mLegacy8259;

//Copied from \MdeModulePkg\Library\DxePrintLibPrint2Protocol\PrintLib.c
UINTN
EFIAPI
MyPrint ()
{
  CHAR16  *Buffer=L"1 2 3 4 5 6 7 8 9 0 A B C E D F ";
  
  UnicodeSPrint(Buffer,16,L"%d\r\n",Image->Var2);
  gST->ConOut->OutputString(gST->ConOut,Buffer); 
  
  return 0;
}

//
// Worker Functions
//
/**
  Sets the counter value for Timer #0 in a legacy 8254 timer.

  @param Count    The 16-bit counter value to program into Timer #0 of the legacy 8254 timer.
**/
VOID
SetPitCount (
  IN UINT16  Count
  )
{
  IoWrite8 (TIMER_CONTROL_PORT, 0x36);
  IoWrite8 (TIMER0_COUNT_PORT, (UINT8)(Count & 0xff));
  IoWrite8 (TIMER0_COUNT_PORT, (UINT8)((Count >> 8) & 0xff));
}

/**

  This function adjusts the period of timer interrupts to the value specified
  by TimerPeriod.  If the timer period is updated, then the selected timer
  period is stored in EFI_TIMER.TimerPeriod, and EFI_SUCCESS is returned.  If
  the timer hardware is not programmable, then EFI_UNSUPPORTED is returned.
  If an error occurs while attempting to update the timer period, then the
  timer hardware will be put back in its state prior to this call, and
  EFI_DEVICE_ERROR is returned.  If TimerPeriod is 0, then the timer interrupt
  is disabled.  This is not the same as disabling the CPU's interrupts.
  Instead, it must either turn off the timer hardware, or it must adjust the
  interrupt controller so that a CPU interrupt is not generated when the timer
  interrupt fires.


  @param This            The EFI_TIMER_ARCH_PROTOCOL instance.
  @param TimerPeriod     The rate to program the timer interrupt in 100 nS units.  If
                         the timer hardware is not programmable, then EFI_UNSUPPORTED is
                         returned.  If the timer is programmable, then the timer period
                         will be rounded up to the nearest timer period that is supported
                         by the timer hardware.  If TimerPeriod is set to 0, then the
                         timer interrupts will be disabled.

  @retval        EFI_SUCCESS       The timer period was changed.
  @retval        EFI_UNSUPPORTED   The platform cannot change the period of the timer interrupt.
  @retval        EFI_DEVICE_ERROR  The timer period could not be changed due to a device error.

**/
EFI_STATUS
EFIAPI
TimerDriverSetTimerPeriod (
  IN UINT64                   TimerPeriod
  )
{
  UINT64  TimerCount;

  //
  //  The basic clock is 1.19318 MHz or 0.119318 ticks per 100 ns.
  //  TimerPeriod * 0.119318 = 8254 timer divisor. Using integer arithmetic
  //  TimerCount = (TimerPeriod * 119318)/1000000.
  //
  //  Round up to next highest integer. This guarantees that the timer is
  //  equal to or slightly longer than the requested time.
  //  TimerCount = ((TimerPeriod * 119318) + 500000)/1000000
  //
  // Note that a TimerCount of 0 is equivalent to a count of 65,536
  //
  // Since TimerCount is limited to 16 bits for IA32, TimerPeriod is limited
  // to 20 bits.
  //
  if (TimerPeriod == 0) {
    //
    // Disable timer interrupt for a TimerPeriod of 0
    //
    mLegacy8259->DisableIrq (mLegacy8259, Efi8259Irq0);
  } else {

    //
    // Convert TimerPeriod into 8254 counts
    //
    TimerCount = DivU64x32 (MultU64x32 (119318, (UINT32) TimerPeriod) + 500000, 1000000);

    //
    // Check for overflow
    //
    if (TimerCount >= 65536) {
      TimerCount = 0;
      TimerPeriod = MAX_TIMER_TICK_DURATION;
    }
    //
    // Program the 8254 timer with the new count value
    //
    SetPitCount ((UINT16) TimerCount);

    //
    // Enable timer interrupt
    //
    mLegacy8259->EnableIrq (mLegacy8259, Efi8259Irq0, FALSE);
  }
  //
  // Save the new timer period
  //
  mTimerPeriod = TimerPeriod;

  return EFI_SUCCESS;
}


VOID
EFIAPI
MyInterruptHandler (
  IN EFI_EXCEPTION_TYPE   InterruptType,
  IN EFI_SYSTEM_CONTEXT   SystemContext
  )
{
          EFI_TPL OriginalTPL;

  OriginalTPL = gBS->RaiseTPL (TPL_HIGH_LEVEL);

  mLegacy8259->EndOfInterrupt (mLegacy8259, Efi8259Irq0);

       Image->Var2++; 

  gBS->RestoreTPL (OriginalTPL);

}  
/**
  The user Entry Point for Print module.

  This is the entry point for Print DXE Driver. It installs the Print2 Protocol.

  @param[in] ImageHandle    The firmware allocated handle for the EFI image.
  @param[in] SystemTable    A pointer to the EFI System Table.

  @retval EFI_SUCCESS       The entry point is executed successfully.
  @retval Others            Some error occurs when executing this entry point.

**/
EFI_STATUS
EFIAPI
PrintEntryPoint (
  IN EFI_HANDLE           ImageHandle,
  IN EFI_SYSTEM_TABLE     *SystemTable
  )
{
        EFI_STATUS              Status;
        EFI_CPU_ARCH_PROTOCOL  *mCpu;
        UINT32                  TimerVector;

        //
        // Allocate a new image structure
        //
        Image = AllocateZeroPool (sizeof(EFI_PRINT9_PRIVATE_DATA));
        if (Image == NULL) {
                Status = EFI_OUT_OF_RESOURCES;
                ASSERT_EFI_ERROR (Status);
        }

        Image->Signature         = PRINT9_PRIVATE_DATA_SIGNATURE;
  
        Image->PRINT9.UnicodeBSPrint=UnicodeBSPrint;
        Image->PRINT9.UnicodeSPrint=UnicodeSPrint;
        Image->PRINT9.UnicodeBSPrintAsciiFormat=UnicodeBSPrintAsciiFormat;
        Image->PRINT9.UnicodeSPrintAsciiFormat=MyPrint;
        //Image->PRINT9.UnicodeValueToString=UnicodeValueToString;
        Image->PRINT9.AsciiBSPrint=AsciiBSPrint;
        Image->PRINT9.AsciiSPrint=AsciiSPrint;        
        Image->PRINT9.AsciiBSPrintUnicodeFormat=AsciiBSPrintUnicodeFormat;
        Image->PRINT9.AsciiSPrintUnicodeFormat=AsciiSPrintUnicodeFormat;
        //Image->PRINT9.AsciiValueToString=AsciiValueToString;

        Image->Var2=1984;
        
        Status = gBS->InstallMultipleProtocolInterfaces (
                  &mPrintThunkHandle,
                  &gEfiPrint9ProtocolGuid, 
                  &Image->PRINT9,
                  NULL
                );
        ASSERT_EFI_ERROR (Status);

        //
        // Locate the Cpu Arch Protocol.
        //
        Status = gBS->LocateProtocol (&gEfiCpuArchProtocolGuid, NULL, &mCpu);
        ASSERT_EFI_ERROR (Status);

        //
        // Find the Legacy8259 protocol.
        //
        Status = gBS->LocateProtocol (&gEfiLegacy8259ProtocolGuid, NULL, (VOID **) &mLegacy8259);
        ASSERT_EFI_ERROR (Status);        
        
        //
        // Force the timer to be disabled
        //
        Status = TimerDriverSetTimerPeriod (0);
        ASSERT_EFI_ERROR (Status);        
        
        //
        // Get the interrupt vector number corresponding to IRQ0 from the 8259 driver
        //
        TimerVector = 0;
        Status      = mLegacy8259->GetVector (mLegacy8259, Efi8259Irq0, (UINT8 *) &TimerVector);
        ASSERT_EFI_ERROR (Status);

        //
        // Install interrupt handler for 8254 Timer #0 (ISA IRQ0)
        //
        Status = mCpu->RegisterInterruptHandler (mCpu, TimerVector, MyInterruptHandler);
        ASSERT_EFI_ERROR (Status);

        //
        // Force the timer to be enabled at its default period
        //
        Status = TimerDriverSetTimerPeriod (DEFAULT_TIMER_TICK_DURATION);
        ASSERT_EFI_ERROR (Status);        
        
  return Status;
}

 

运行结果(实体机):
mi4
从运行结果来看,代码中设定的定时器能够正常工作和我们的预期一致。
8254初始化部分代码拷贝自 8254TimerDxe\Timer.c ,很多具体操作的含义并不清楚,有兴趣的朋友可以找一本《微机原理》的书籍对照研究一下。X86架构依然兼容很久之前的 8254、8259的设计。这样的兼容性既是X86的优点,保证了过去的代码依然能够运行;同时也是X86沉重的历史包袱,而后面这一点更加是BIOS存在的意义。

EFI (X64)下载
printdriver4
源代码
PrintDriver4SRC

参考:
1. Intel® Platform Innovation Framework for UEFI Compatibility Support Module Specification

Arduino Leonardo 板子上的 LED 分析

正经的 Arduino Leonardo 上面有四个LED(DFrobot 的Leonardo&XBEE V1.2 上面有5个LED)。长得是下面这个样子:

led1

对照电路图 LED 连接如下:

led2

LED 控制Pin 说明
L D13 (PC7) GPIO 控制
TX PD5 USB发送数据亮
RX PB0 USB接收数据亮
ON 5V 上电就亮

初始化在 \arduino-1.8.4\hardware\arduino\avr\cores\arduino\USBCore.cpp 有定义

void USBDevice_::attach()
{
	_usbConfiguration = 0;
	_usbCurrentStatus = 0;
	_usbSuspendState = 0;
	USB_ClockEnable();

	UDINT &= ~((1<<WAKEUPI) | (1<<SUSPI)); // clear already pending WAKEUP / SUSPEND requests
	UDIEN = (1<<EORSTE) | (1<<SOFE) | (1<<SUSPE);	// Enable interrupts for EOR (End of Reset), SOF (start of frame) and SUSPEND
	
	TX_RX_LED_INIT;

#if MAGIC_KEY_POS != (RAMEND-1)
	if (pgm_read_word(FLASHEND - 1) == NEW_LUFA_SIGNATURE) {
		_updatedLUFAbootloader = true;
	}
#endif
}

 

其中的 一些宏定义在 \arduino-1.8.4\hardware\arduino\avr\variants\leonardo\pins_arduino.h

#define TX_RX_LED_INIT	DDRD |= (1<<5), DDRB |= (1<<0)
#define TXLED0			PORTD |= (1<<5)
#define TXLED1			PORTD &= ~(1<<5)
#define RXLED0			PORTB |= (1<<0)
#define RXLED1			PORTB &= ~(1<<0)

 

Tx/Rx 的作用是用来指示传输,而实际上传输是一下就发生和结束的,因此,设计上使用了一个延时

/** Pulse generation counters to keep track of the number of milliseconds remaining for each pulse type */
#define TX_RX_LED_PULSE_MS 100
volatile u8 TxLEDPulse; /**< Milliseconds remaining for data Tx LED pulse */
volatile u8 RxLEDPulse; /**< Milliseconds remaining for data Rx LED pulse */

 

当收到数据时,就Enable Led,同时重置计时器

static inline void Recv(volatile u8* data, u8 count)
{
	while (count--)


		*data++ = UEDATX;
	
	RXLED1;					// light the RX LED
	RxLEDPulse = TX_RX_LED_PULSE_MS;	
}

static inline u8 Recv8()
{
	RXLED1;					// light the RX LED
	RxLEDPulse = TX_RX_LED_PULSE_MS;

	return UEDATX;	
}
在每一个 SOF 的时候检查LED
	//	Start of Frame - happens every millisecond so we use it for TX and RX LED one-shot timing, too
	if (udint & (1<<SOFI))
	{
		USB_Flush(CDC_TX);				// Send a tx frame if found
		
		// check whether the one-shot period has elapsed.  if so, turn off the LED
		if (TxLEDPulse && !(--TxLEDPulse))
			TXLED0;
		if (RxLEDPulse && !(--RxLEDPulse))
			RXLED0;
	}

 

例子:

arduino-1.8.4\hardware\arduino\avr\variants\leonardo\pins_arduino.h

#define NUM_DIGITAL_PINS  31
#define NUM_ANALOG_INPUTS 12

/*
#define TX_RX_LED_INIT	DDRD |= (1<<5), DDRB |= (1<<0)
#define TXLED0			PORTD |= (1<<5)
#define TXLED1			PORTD &= ~(1<<5)
#define RXLED0			PORTB |= (1<<0)
#define RXLED1			PORTB &= ~(1<<0)
*/

//labz_Start
#define TX_RX_LED_INIT	DDRD |= (1<<5), DDRB |= (1<<0),DDRF = 0x81
#define TXLED0			PORTD |= (1<<5);PORTF &= ~ 0x01
#define TXLED1			PORTD &= ~(1<<5);PORTF |= 0x01
#define RXLED0			PORTB |= (1<<0);PORTF &= ~ 0x80 
#define RXLED1			PORTB &= ~(1<<0);PORTF |= 0x80
// labz _End

#define PIN_WIRE_SDA         (2)
#define PIN_WIRE_SCL         (3)


// the setup function runs once when you press reset or power the board
void setup() {
  // initialize digital pin LED_BUILTIN as an output.
  pinMode(LED_BUILTIN, OUTPUT);
}

// the loop function runs over and over again forever
void loop() {
  while (Serial.available())
    {  byte c=Serial.read();
       Serial.write(c);
      }
}

 

参考:
1.Leonardo 电路图
https://www.arduino.cc/en/uploads/Main/arduino-leonardo-schematic_3b.pdf
2.引脚关系
http://www.zembedded.com/wp-content/uploads/2013/04/Ardunio_leonardo.png

Step to UEFI (133)再试验 EFI_CPU_ARCH_PROTOCOL

前面提到了 EFI_CPU_ARCH_PROTOCOL ,这次试试这个Protocol的 EnableInterrput 和 DisableInterrupt。

ma1

实验的方法是使用 CpuSleep() 这个函数,在 \UDK2017\MdePkg\Library\BaseCpuLib\X64\CpuSleep.asm 中可以看到具体实现:

    .code
;------------------------------------------------------------------------------
; VOID
; EFIAPI
; CpuSleep (
;   VOID
;   );
;------------------------------------------------------------------------------
CpuSleep    PROC
    hlt
    ret
CpuSleep    ENDP

END

 

就是说他调用了 hlt 这个指令,这个指令的介绍如下,执行这个指令后 CPU 会停机,只有中断才能唤醒CPU。我们在这个函数之后再编写输出字符串的代码,如果能看到就说明CPU被中断唤醒了(更简单的判断:没唤醒就会死机)。
cpp2

最终的代码如下:

/** @file
    A simple, basic, application showing how the Hello application could be
    built using the "Standard C Libraries" from StdLib.

    Copyright (c) 2010 - 2011, Intel Corporation. All rights reserved.<BR>
    This program and the accompanying materials
    are licensed and made available under the terms and conditions of the BSD License
    which accompanies this distribution. The full text of the license may be found at
    http://opensource.org/licenses/bsd-license.

    THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
    WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
**/
#include <Library/BaseLib.h>
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/PrintLib.h>
#include <Library/ShellCEntryLib.h>

#include <Protocol/Cpu.h>
#include <Library/CpuLib.h>

EFI_GUID gEfiCpuArchProtocolGuid = 
                { 0x26BACCB1, 0x6F42, 0x11D4, 
                        { 0xBC, 0xE7, 0x00, 0x80, 0xC7, 0x3C, 0x88, 0x81 }};
                        
extern EFI_BOOT_SERVICES         *gBS;

/***
  Demonstrates basic workings of the main() function by displaying a
  welcoming message.

  Note that the UEFI command line is composed of 16-bit UCS2 wide characters.
  The easiest way to access the command line parameters is to cast Argv as:
      wchar_t **wArgv = (wchar_t **)Argv;

  @param[in]  Argc    Number of argument tokens pointed to by Argv.
  @param[in]  Argv    Array of Argc pointers to command line tokens.

  @retval  0         The application exited normally.
  @retval  Other     An error occurred.
***/
int
main (
  IN int Argc,
  IN char **Argv
  )
{
  EFI_CPU_ARCH_PROTOCOL  *Cpu;
  EFI_STATUS             Status;
  
  //
  // Locate the Cpu Arch Protocol.
  //
  Status = gBS->LocateProtocol (&gEfiCpuArchProtocolGuid, NULL, &Cpu);
  if (EFI_ERROR (Status)) {
    Print(L"Can't find EFI_CPU_ARCH_PROTOCOL\n");
    return Status;
  }

  Print(L"Running CpuSleep\n");
  CpuSleep();
  Print(L"CpuSleep Exit\n");

  Print(L"Disable CPU interrupt by EFI_CPU_ARCH_PROTOCOL\n");  
  Cpu->DisableInterrupt(Cpu);
  Print(L"Running CpuSleep\n");
  CpuSleep();
  Print(L"CpuSleep Exit\n");
  
  Print(L"Disable CPU interrupt by CLI\n");  
  Print(L"Running CpuSleep\n");  
  DisableInterrupts();
  CpuSleep();  
  EnableInterrupts();  
  Print(L"CpuSleep Exit\n");
  
  return 0;
}

 

运行结果如下(这是在实体机运行的结果):
cpp3
代码中调用了 CpuSleep 函数三次,第一次应该是被UEFI 中的Timer唤醒了,所以运行一下就出来了;第二次调用 CpuSleep函数之前,先用EFI_CPU_ARCH_PROTOCOL 的 DisableInterrupt,但是并没有作用;第三次,我们用CLI 指令关闭所有的中断,于是,程序停下来了,也死机了。

完整的代码下载:

CPUArchTest2

结论: 保险起见,如果你想 Disable Interrupt,那么请使用 cli 这样的指令。后面有空再探究一下为什么这两个函数没有效果。

使用 USB3.0 线 做WinDBG debug

Windows App Store 中推出了新版的 WinDBG,功能上应该和 WDK之类的相同,界面变化很大。

image001
image002

首先用 WinDBG USB 3.0 线将Host(控制端,运行 WinDBG)和Slave(被控制端)连接起来;和之前 USB2.0 的Debug完全不同,Slave上任何一个USB3.0端口都可以,不需要特别查找 Debug Port 。

下图为 Intel Kabylake HDK 平台
image004

下图为我使用的笔记本工作机
image003

之后,需要在 Slave 上MSCONFIG里面做一些设置:
image005

USB target name 设定为 labz,后面 WinDBG的设置也会使用到。设置之后,Slave会要求重启。
启动Host上面的 WinDBG,使用搜索功能,找到长这样的(一个系统中可以安装多个版本的 WinDBG)

image006

界面和之前相比变化很大,看起来没有那么死板了

image007

在 File 中连接的选项,选择 Attach to kernel, USB 页面Target Name 填写上 labz

image008

点击OK按钮之后,HOST和SLAVE即连接起来了

image009

使用 Break 按钮可以停下来
如果发现无法连接,请检查 HOST 的设备管理器 USB Debug Connection Device设备是否有Yellow Bang。 我遇到了这样的情况,显示的错误信息是驱动未签名

image010

例如:

image011

解决方法是:找一个有签名的驱动安装一下。这里放一个我目前在用的,可以试试看

WinDBGUSBDriver(旧版本)

2025年5月 更新一个我这边可以用的驱动  下载

====================================================

20201124 最近开始使用 WinDBG 惊奇的发现又出现了上面提到的问题,经过研究(https://answersweb.azurewebsites.net/MVC/Post/Thread/045cf703-1dbc-4f3f-9557-cba72af2f548?category=wdk),同样应该是驱动版本导致的。

解决方法是找到最新的 SDK 安装之,然后在安装目录下(默认在C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\usb)可以找到最新的驱动。然后问题就可以解决了。

Step to UEFI (132)RamDisk Application 例子

前面提到过 Ram Disk Protocol,这次尝试编写一个 Application 来完成创建 RamDisk 的动作。
首先看一下 Specification:

rd1
rd2

这个 Protocol 提供了2个函数,一个用来注册 RAM Disk 的Register,一个用来销毁 RAM Disk 的 Unregister。对于我们来说,注册的函数是最重要的。

rd3

注册一个Ram Disk 需要给定:
RamDiskBase: 新的 Ram Disk 的基地址
RamDiskSize: 新的 Ram Disk 的大小
RamDiskType: 新的 Ram Disk 的类型(似乎可以定义 ISO/RAW之类的)
ParentDevicePath: 指向父设备的 Device Path(不明白这个功能有什么意义)。如果没有可以设置为 NULL
DevicePath: 返回的创建的 Ram Disk 的 Device Path

有了上面的信息,我们即可完成创建工作。

编写一个测试代码,步骤如下:
1. 查找 RamDiskProtocol
2. 读取 “”MemTest.Img”到内存中
3. 用 RamDiskProtocol 的 Register 函数将上面的内存注册为 Ram Disk

完整代码:

/** @file
    A simple, basic, application showing how the Hello application could be
    built using the "Standard C Libraries" from StdLib.

    Copyright (c) 2010 - 2011, Intel Corporation. All rights reserved.<BR>
    This program and the accompanying materials
    are licensed and made available under the terms and conditions of the BSD License
    which accompanies this distribution. The full text of the license may be found at
    http://opensource.org/licenses/bsd-license.

    THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
    WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
**/
#include <Library/BaseLib.h>
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/PrintLib.h>
#include <Library/ShellCEntryLib.h>

#include <Protocol/RamDisk.h>

#include <Protocol/DevicePathToText.h>

#include <Protocol/EfiShell.h>
#include <Library/ShellLib.h>

extern EFI_BOOT_SERVICES         *gBS;

/*
EFI_GUID gEfiVirtualDiskGuid = 
           { 0x77AB535A, 0x45FC, 0x624B, 
                {0x55, 0x60, 0xF7, 0xB2, 0x81, 0xD1, 0xF9, 0x6E }};
   */             
/***
  Demonstrates basic workings of the main() function by displaying a
  welcoming message.

  Note that the UEFI command line is composed of 16-bit UCS2 wide characters.
  The easiest way to access the command line parameters is to cast Argv as:
      wchar_t **wArgv = (wchar_t **)Argv;

  @param[in]  Argc    Number of argument tokens pointed to by Argv.
  @param[in]  Argv    Array of Argc pointers to command line tokens.

  @retval  0         The application exited normally.
  @retval  Other     An error occurred.
***/
int
main (
  IN int Argc,
  IN char **Argv
  )
{
        EFI_STATUS               Status;
        EFI_RAM_DISK_PROTOCOL    *MyRamDisk;
        UINT64                   *StartingAddr;
        EFI_DEVICE_PATH_PROTOCOL *DevicePath;
        EFI_FILE_HANDLE          FileHandle;
        EFI_FILE_INFO            *FileInfo;  
        UINTN                    ReadSize; 
  
  // Look for Ram Disk Protocol
        Status = gBS->LocateProtocol (
                        &gEfiRamDiskProtocolGuid,
                        NULL,
                        &MyRamDisk
                 );
        if (EFI_ERROR (Status)) {
            Print(L"Couldn't find RamDiskProtocol\n");
            return EFI_ALREADY_STARTED;
        }

  //Open one Image and load it to the memory
        //Open the file given by the parameter
        Status = ShellOpenFileByName(
                        L"MemTest.IMG", 
                        (SHELL_FILE_HANDLE *)&FileHandle,
                        EFI_FILE_MODE_READ, 
                        0);
        if(EFI_ERROR (Status)) {
                Print(L"OpenFile failed! Error=[%r]\n",Status);
                return EFI_SUCCESS;
        }                                                        
  
        //Get file size
        FileInfo = ShellGetFileInfo((SHELL_FILE_HANDLE)FileHandle);    
        
        //Allocate a memory for Image
        Status = gBS->AllocatePool (
                    EfiReservedMemoryType,
                    (UINTN)FileInfo-> FileSize,
                    (VOID**)&StartingAddr
                    ); 
        if(EFI_ERROR (Status)) {
                Print(L"Allocate Memory failed!\n");
                return EFI_SUCCESS;
        } 
        
        //Load the whole file to the buffer
        Status = ShellReadFile(FileHandle,&ReadSize,StartingAddr);
        if(EFI_ERROR (Status)) {
                Print(L"Read file failed!\n");
                return EFI_SUCCESS;
        } 
        
        //
        // Register the newly created RAM disk.
        //
        Status = MyRamDisk->Register (
             ((UINT64)(UINTN) StartingAddr),
             FileInfo-> FileSize,
             &gEfiVirtualDiskGuid,
             NULL,
             &DevicePath
             );
        if (EFI_ERROR (Status)) {
                Print(L"Can't create RAM Disk!\n");
                return EFI_SUCCESS;
        }

        //Show RamDisk DevicePath
        {
                EFI_DEVICE_PATH_TO_TEXT_PROTOCOL* Device2TextProtocol;
                CHAR16*                           TextDevicePath = 0;
                Status = gBS->LocateProtocol(
                             &gEfiDevicePathToTextProtocolGuid,
                             NULL,
                             (VOID**)&Device2TextProtocol
                        );  
                TextDevicePath = 
                        Device2TextProtocol->ConvertDevicePathToText(DevicePath, FALSE, TRUE); 
                Print(L"DevicePath=%s\n", TextDevicePath);
                Print(L"Disk Size =%d Bytes\n", FileInfo-> FileSize);
                if(TextDevicePath)gBS->FreePool(TextDevicePath);
        }      
  
  Print(L"Creat Ram Disk success!\n");
  return 0;
}

 

运行结果:
rd4

首先,可以看到系统中只有一个 Fs0:, 运行 mrd.efi 之后,系统中多了一个 Fs1,其中的内容就是 MemTest.Img的内容(使用的文件和之前的MemTest.Img 是相同的)。

编译生成的 X64 Application:
mrd

完整的代码:
MyRamDisk

Step to UEFI (131)gBS 的 Stall 探究

gBS 提供的 Stall 函数是我们经常用来做延时的过程。下面就介绍一下这个函数在NT32Pkg 中的具体实现。因为涉及到了具体的实现代码,所以列出来篇幅很长,对于大多数朋友来说直接看中文部分介绍就足够了。

首先,找到原型的定义,在 \MdeModulePkg\Core\Dxe\DxeMain\DxeMain.c

//
// DXE Core Module Variables
//
EFI_BOOT_SERVICES mBootServices = {
.........
  (EFI_EXIT_BOOT_SERVICES)                      CoreExitBootServices,                     // ExitBootServices
  (EFI_GET_NEXT_MONOTONIC_COUNT)                CoreEfiNotAvailableYetArg1,               // GetNextMonotonicCount
  (EFI_STALL)                                   CoreStall,                                // Stall
  (EFI_SET_WATCHDOG_TIMER)                      CoreSetWatchdogTimer,                     // SetWatchdogTimer
  (EFI_CONNECT_CONTROLLER)                      CoreConnectController,                    // ConnectController
.........

 

CoreStall 定义在 \MdeModulePkg\Core\Dxe\Misc\Stall.c 文件中。他接收的参数是暂停多少微秒,在这个函数中,防止计算溢出分别处理了休眠的事件很短和很长的情况,实际完成每一个单位的暂停是同一个函数:CoreInternalWaitForTick

EFI_STATUS
EFIAPI
CoreStall (
  IN UINTN            Microseconds
  )
{
  UINT64  Counter;
  UINT32  Remainder;
  UINTN   Index;

  if (gMetronome == NULL) {
    return EFI_NOT_AVAILABLE_YET;
  }

  //
  // Counter = Microseconds * 10 / gMetronome->TickPeriod
  // 0x1999999999999999 = (2^64 - 1) / 10
  //
  if ((UINT64) Microseconds > 0x1999999999999999ULL) {
    //
    // Microseconds is too large to multiple by 10 first.  Perform the divide 
    // operation first and loop 10 times to avoid 64-bit math overflow.
    //
    Counter = DivU64x32Remainder (
                Microseconds,
                gMetronome->TickPeriod,
                &Remainder
                );
    for (Index = 0; Index < 10; Index++) {
      CoreInternalWaitForTick (Counter);
    }      

    if (Remainder != 0) {
      //
      // If Remainder was not zero, then normally, Counter would be rounded 
      // up by 1 tick.  In this case, since a loop for 10 counts was used
      // to emulate the multiply by 10 operation, Counter needs to be rounded
      // up by 10 counts.
      //
      CoreInternalWaitForTick (10);
    }
  } else {
    //
    // Calculate the number of ticks by dividing the number of microseconds by
    // the TickPeriod.  Calculation is based on 100ns unit.
    //
    Counter = DivU64x32Remainder (
                MultU64x32 (Microseconds, 10),
                gMetronome->TickPeriod,
                &Remainder
                );
    if (Remainder != 0) {
      //
      // If Remainder is not zero, then round Counter up by one tick.
      //
      Counter++;
    }
    CoreInternalWaitForTick (Counter);
  }

  return EFI_SUCCESS;
}

 

做每一个单位休眠的函数是CoreInternalWaitForTick,他调用的是 Metronome Architectural Protocol。

/**
  Internal worker function to call the Metronome Architectural Protocol for 
  the number of ticks specified by the UINT64 Counter value.  WaitForTick() 
  service of the Metronome Architectural Protocol uses a UINT32 for the number
  of ticks to wait, so this function loops when Counter is larger than 0xffffffff.

  @param  Counter           Number of ticks to wait.

**/
VOID
CoreInternalWaitForTick (
  IN UINT64  Counter
  )
{
  while (RShiftU64 (Counter, 32) > 0) {
    gMetronome->WaitForTick (gMetronome, 0xffffffff);
    Counter -= 0xffffffff;
  }
  gMetronome->WaitForTick (gMetronome, (UINT32)Counter);
}

 

gMetronome 是EFI_METRONOME_ARCH_PROTOCOL ,在 \MdeModulePkg\Core\Dxe\DxeMain\DxeMain.c 中

EFI_METRONOME_ARCH_PROTOCOL       *gMetronome     = NULL;

 

具体这个 Protocol 可以在 PI Specification 中找到:
ma1

他的成员包括一个函数和一个变量, WaitForTick 是用来做实际延时的, TickPeriod 是用来说明WaitForTick函数的单位的。TickPeriod的单位是100ns,最长不能超过 200us。为了实验,编写下面的Application:

/** @file
    A simple, basic, application showing how the Hello application could be
    built using the "Standard C Libraries" from StdLib.

    Copyright (c) 2010 - 2011, Intel Corporation. All rights reserved.<BR>
    This program and the accompanying materials
    are licensed and made available under the terms and conditions of the BSD License
    which accompanies this distribution. The full text of the license may be found at
    http://opensource.org/licenses/bsd-license.

    THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
    WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
**/
#include <Library/BaseLib.h>
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/PrintLib.h>
#include <Library/ShellCEntryLib.h>

#include <Protocol/Metronome.h>

EFI_GUID gEfiMetronomeArchProtocolGuid  = 
                { 0x26BACCB2, 0x6F42, 0x11D4, 
                        { 0xBC, 0xE7, 0x00, 0x80, 0xC7, 0x3C, 0x88, 0x81 }};
                        
extern EFI_BOOT_SERVICES         *gBS;

/***
  Demonstrates basic workings of the main() function by displaying a
  welcoming message.

  Note that the UEFI command line is composed of 16-bit UCS2 wide characters.
  The easiest way to access the command line parameters is to cast Argv as:
      wchar_t **wArgv = (wchar_t **)Argv;

  @param[in]  Argc    Number of argument tokens pointed to by Argv.
  @param[in]  Argv    Array of Argc pointers to command line tokens.

  @retval  0         The application exited normally.
  @retval  Other     An error occurred.
***/
int
main (
  IN int Argc,
  IN char **Argv
  )
{
  EFI_METRONOME_ARCH_PROTOCOL   *Metronome;
  EFI_STATUS                    Status;
  
  //
  // Locate the Cpu Arch Protocol.
  //
  Status = gBS->LocateProtocol (&gEfiMetronomeArchProtocolGuid, NULL, &Metronome);
  if (EFI_ERROR (Status)) {
    Print(L"Can't find EFI_METRONOME_ARCH_PROTOCOL\n");
    return Status;
  }

  Print(L"TickPeriod=%d\n",Metronome->TickPeriod);
  
  return 0;
}

 

运行之后输出结果如下:
ma2
意思是,在 NT32 模拟环境中,一个Tick的时长是 2000*100ns=200us。在Code中,这个值是固定的,在\Nt32Pkg\MetronomeDxe\Metronome.c 定了下面这个结构体:

//
// Global Variables
//
EFI_METRONOME_ARCH_PROTOCOL mMetronome = {
  WinNtMetronomeDriverWaitForTick,
  TICK_PERIOD
};

 

在 \Nt32Pkg\MetronomeDxe\Metronome.h 中给定了TICK_PERIOD 的值,如果把这个定义修改为 2001,那么再次运行 Application ,输出值也会变化

//
// Period of on tick in 100 nanosecond units
//
#define TICK_PERIOD 2000

 

同样的,处理NT32 “硬件”延时相关的代码就是WinNtMetronomeDriverWaitForTick,在\Nt32Pkg\MetronomeDxe\Metronome.c 文件中:

EFI_STATUS
EFIAPI
WinNtMetronomeDriverWaitForTick (
  IN EFI_METRONOME_ARCH_PROTOCOL  *This,
  IN UINT32                       TickNumber
  )
/*++

Routine Description:

  The WaitForTick() function waits for the number of ticks specified by
  TickNumber from a known time source in the platform.  If TickNumber of
  ticks are detected, then EFI_SUCCESS is returned.  The actual time passed
  between entry of this function and the first tick is between 0 and
  TickPeriod 100 nS units.  If you want to guarantee that at least TickPeriod
  time has elapsed, wait for two ticks.  This function waits for a hardware
  event to determine when a tick occurs.  It is possible for interrupt
  processing, or exception processing to interrupt the execution of the
  WaitForTick() function.  Depending on the hardware source for the ticks, it
  is possible for a tick to be missed.  This function cannot guarantee that
  ticks will not be missed.  If a timeout occurs waiting for the specified
  number of ticks, then EFI_TIMEOUT is returned.

Arguments:

  This       - The EFI_METRONOME_ARCH_PROTOCOL instance.
  TickNumber - Number of ticks to wait.

Returns:

  EFI_SUCCESS - The wait for the number of ticks specified by TickNumber
                succeeded.

--*/
{
  UINT64  SleepTime;

  //
  // Calculate the time to sleep.  Win API smallest unit to sleep is 1 millisec
  // Tick Period is in 100ns units, divide by 10000 to convert to ms
  //
  SleepTime = DivU64x32 (MultU64x32 ((UINT64) TickNumber, TICK_PERIOD) + 9999, 10000);
  gWinNt->Sleep ((UINT32) SleepTime);

  return EFI_SUCCESS;
}

 

可以看到,Nt32 虚拟机中最终是使用 Sleep 来实现的,这个Api 最小取值为 1毫秒(千分之一),在编写一些在 NT32模拟环境下运行的程序时,也要特别注意他延时的最小单位只有1毫秒。

对应在具体的实体机上,会有更加精确的延时,有兴趣的朋友不妨追踪一下手中的代码,看看具体是如何实现的。

本文提到的 Application 下载:

METRONOMEARCHTest