[轉] UEFI实战

UEFI实战(1)

出處:http://www.cppblog.com/djxzh/

本篇为UEFI实战系列第一部分。
UEFI实战前10个部分计划如下:
UEFI 实战(1) 开发环境 

讲述如何配置开发环境。
UEFI 实战(2)  HelloWorld
讲述dsc, inf文件的格式, application常用的变量,数据结构和函数。
UEFI 实战(3) C++
讲述如何用C++开发UEFI程序。
UEFI 实战(4) protocol
讲述UEFI中protocol的概念
UEFI实战(5) driver
讲述UEFI中driver模型
UEFI实战(6) 文件读写
UEFI实战(7) Hii interface 之 Form
UEFI实战(8) Hii interface 之 String 和Font
UEFI实战(9) GUI
UEFI实战(10) Network

UEFI 实战(1)

配置开发环境
1. 下载 Windows SDK
2. 下载EDK2 
3. 打开visual studio 2008 command prompt 
    cd EDK2
    edksetup.bat
4. 编辑Conftaget.txt, 修改 编译工具TOOL_CHAIN_TAG        为
   TOOL_CHAIN_TAG        = VS2008x86
5. build
    build命令有两个参数, -a 和-p, -a 用来选择平台(IA32 X64,…) -p用来选择要编译的package,默认的package是Nt32Pkg, 所以build命令与 build -a IA32 -p Nt32PkgNt32Pkg.dsc 等同,用来编译UEFI模拟器。
6. build run
    与build -a IA32 -p Nt32PkgNt32Pkg.dsc run 命令等同,用来运行UEFI模拟器。
制作UEFI USB启动盘
分两种情况,如果目标平台是UEFI平台,按如下步骤来做:
1。 格式化U盘为FAT(FAT,FAT16, FAT32)格式 
2。 在U盘上建立目录 efiboot
3。 将efi的应用程序 copy到 efiboot 目录,并改名为bootx64.efi 或者bootia32.efi。
      因为UEFI的启动文件是FAT盘内efiboot目录里的bootx64.efi 或bootia32.efi, 与legacy bios需要MBR来引导OS不同。
如果目标平台是legacy bios, 需要在U盘中制作MBR和引导文件, 按如下步骤来做:
1。 编译duet package
      build -a IA32 -p DuetPkgDuetPkgIa32.dsc 或者
      build -a X64 -p DuetPkgDuetPkgX64.dsc
2。 生成引导文件
      cd DuetPkg
      postbuild.bat Ia32 或者 postbuild.bat X64
3。 插入U盘,假设J:是U盘, 向U盘写入MBR
      createbootdisk usb J: FAT32 IA32 或者 createbootdisk usb J: FAT32 X64     
4。拔出并重新插入U盘, 向U盘copy UEFI文件
      createbootdisk usb J: FAT32 IA32 step2 或者 createbootdisk usb J: FAT32 X64 step2
      此命令向U盘根目录copy了efildr20, 该文件用于引导系统进入UEFI环境,并向efiboot目录copy了引导文件bootia32.efi或bootx64.efi
接下来就可以用U盘来运行UEFI了。
如何Debug
UEFI有两种debug方式,一是在模拟环境Nt32Pkg下debug,另一种是通过串口调试真实环境中的UEFI程序。我从来没见过传说中的利用串口调试,所以下面只能说说Nt32Pkg下的debug。
在需要调试的代码前面加入_asm int 3; 编译,然后再模拟环境中(Nt32Pkg)中运行该程序,当模拟器执行到int 3;指令时,会弹出对话框,然后就可以调试了。
========================================================================


初识UEFI
按惯例,首先让我们用HelloWorld跟UEFI打个招呼吧
标准application

/*main.c */
#include <Uefi.h>
EFI_STATUS
UefiMain (
          IN EFI_HANDLE        ImageHandle,
          IN EFI_SYSTEM_TABLE  *SystemTable
          )
{
   SystemTable -> ConOut-> OutputString(SystemTable -> ConOut, L”HelloWorldn”);
   return EFI_SUCCESS;
}


有以下几点需要注意:
1。 头文件, 所有的UEFI程序都有include <Uefi.h>
2。 main函数, UEFI 基本Application的main函数是UefiMain
3。 main函数的返回值类型 EFI_STATUS。 在UEFI中基本上所有的返回值类型都是EFI_STATUS。

它本质上是UINTN。 



4。 main函数的参数。.efi 文件加载到内存后称为Image, ImageHandle 用来描述、访问、控制此Image。 第二个参数是SystemTable,它是我们的程序同UEFI内核打交道的桥梁,通过它我们可以使用UEFI提供的各种服务,如Boot Services和 Runtime Services。 SystemTable是UEFI内核中的一个全局结构体。
5。  输出是通过EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL的OutputString服务完成的。 服务(函数)的第一个参数是This指针,指向Protocol本身。 OutputString()的第二个参数是Unicode字符串。
要想编译main.c,我们还需要.inf文件, 在main.c所在的目录下编辑main.inf文件

##

[Defines]
  INF_VERSION                    = 0x00010005
  BASE_NAME                      = main                    #输出文件的名字为 main.efi
  FILE_GUID                        = 6987936E-ED34-ffdb-AE97-1FA5E4ED2117
  MODULE_TYPE                   = UEFI_APPLICATION #模块类型:

UEFI_DRIVER

,

DXE_DRIVER

,

DXE_RUNTIME_DRIVER

,

UEFI_APPLICATION

,BASE,等
  VERSION_STRING               = 1.0
  ENTRY_POINT                    = UefiMain               #入口函数

#
# The following information is for reference only and not required by the build tools.
#
#  VALID_ARCHITECTURES           = IA32 X64 IPF EBC
#

# 源文件
[Sources]
   main.c

# .dec里面定义 include的路径
[Packages]
  MdePkg/MdePkg.dec

#要链接的库
[LibraryClasses]
  UefiApplicationEntryPoint
  UefiLib

[Protocols]
[FeaturePcd]
[Pcd.common]
[Guids]

#编译选项, = 表示选项附加到默认选项后面。 == 表示仅使用所定义的选项,弃用默认选项。
[BuildOptions]
  #MSFT:*_*_*_CC_FLAGS ==  /nologo /c /WX /GS- /W4 /Gs32768 /D UNICODE /O1ib2 /GL  /EHs-c- /GR- /GF /Gy /Zi /Gm /D EFI_SPECIFICATION_VERSION=0x0002000A /D TIANO_RELEASE_VERSION=0x00080006 /FAs /Oi-
  #MSFT:*_*_*_CC_FLAGS =   /wd4804
  #MSFT:Debug_*_IA32_CC_FLAGS =
  #MSFT:Debug_*_X64_CC_FLAGS =
  #MSFT:Release_*_IA32_CC_FLAGS =
  #MSFT:Release_*_IA32_CC_FLAGS =
  #MSFT:Release_*_IA32_DLINK_FLAGS =
  #GCC:Release_*_IA32_CC_FLAGS = 

然后将  main.inf 添加到 Nt32Pkg.dsc 或UnixPkg.dsc 的[Components]部分, 例如添加下面一行(example目录在EDK2下)

example/main/main.inf

然后就可以使用BaseTools下的build进行编译了。

Windows下执行
edksetup.bat
build -p Nt32Pkgt32Pkg.dsc -a IA32

Linux 执行
source ./edksetup.sh BaseTools
build -p UnixPkg/UnixPkg.dsc -a IA32

其他类型的inf文件
 (1)  可以看出标准的application处理命令行参数不方便,UEFI提供了帮我们处理命令行参数的入口函数ShellCEntryLib。 我们要实现INTN ShellAppMain(UINTN Argc, CHAR16** Argv) 作为(开发者视角的)入口函数。

/*Main.c */
#include <Uefi.h>
INTN
EFIAPI
ShellAppMain (
  IN UINTN Argc,
  IN CHAR16 **Argv
  )
{
 gST -> ConOut-> OutputString(gST -> ConOut, L”HelloWorldn”);
  return 0;
}

inf文件。 我们需要连接ShellCEntryLib 库。

[Defines]
  INF_VERSION                    = 0x00010006
  BASE_NAME                      = Main
  FILE_GUID                        = 4ea97c46-7491-4dfd-b442-747010f3ce5f
  MODULE_TYPE                   = UEFI_APPLICATION
  VERSION_STRING               = 0.1
  ENTRY_POINT                    = ShellCEntryLib

#
#  VALID_ARCHITECTURES           = IA32 X64 IPF
#

[Sources]
  Main.c

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

[LibraryClasses]
  ShellCEntryLib
  UefiLib

[BuildOptions]
  



(2)使用main函数的application。如果你想像C一样使用main函数,那么你需要用到LibC。 LibC 中提供了ShellAppMain函数,我们要提供 int main(int Argc, char** Argv) 供其调用。

/*Main.c */
#include <Uefi.h>

int
EFIAPI
main (
  IN int Argc,
  IN char **Argv
  )
{

 gST -> ConOut-> OutputString(gST -> ConOut, L”HelloWorldn”); 

  return 0;
}

真正的入口函数是 ShellCEntryLib, 调用过程为  ShellCEntryLib -> ShellAppMain -> main.
inf 文件: 我们需要连接 ShellCEntryLib 和LibC库。

[Defines]
  INF_VERSION                    = 0x00010006
  BASE_NAME                      = Main
  FILE_GUID                        = 4ea97c46-7491-4dfd-b442-747010f3ce5f
  MODULE_TYPE                   = UEFI_APPLICATION
  VERSION_STRING               = 0.1
  ENTRY_POINT                    = ShellCEntryLib

#
#  VALID_ARCHITECTURES           = IA32 X64 IPF
#

[Sources]
  Main.c

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

[LibraryClasses]  
  LibC
  ShellCEntryLib

  UefiLib

[BuildOptions]
  MSFT:*_*_IA32_CC_FLAGS  = /Oi-

还要再说明一点,如果你的程序中用到了printf(…)等等标准C的库函数,那么一定要使用此种类型的application。 因为 ShellCEntryLib 函数中会调用ShellAppMain(…), StdLib的ShellAppMain(…) 会对stdlib 进行初始化。 然后才可以调用stdlib的函数。 (当然,如果你已经清楚地了解了入口函数的处理流程,你也可以手工调用StdLib的ShellAppMain进行出事后).
(3)Lib 模块的inf文件。开发大型工程的时候我们会用到lib,例如我们要开发视频解码程序,会用到zlib库,

[Defines]
  INF_VERSION                    = 0x00010005
  BASE_NAME                      = zlib
  FILE_GUID                        = 348aaa62-BFBD-4882-9ECE-C80BBbbbb736
  VERSION_STRING               = 1.0
  MODULE_TYPE                   = BASE    #Base 表示此模块编译为library
  LIBRARY_CLASS                 = zlib

#
# The following information is for reference only and not required by the build tools.
#
#  VALID_ARCHITECTURES           = IA32 X64 IPF EBC
#

[Sources]
  adler32.c
  crc32.c
  deflate.c
  infback.c
  inffast.c
  inflate.c
  inftrees.c
  trees.c
  zutil.c
  compress.c
  uncompr.c
  gzclose.c
  gzlib.c
  gzread.c
  gzwrite.c

[Packages]
  MdePkg/MdePkg.dec
  MdeModulePkg/MdeModulePkg.dec
  StdLib/StdLib.dec

[LibraryClasses]
  MemoryAllocationLib
  BaseLib
  UefiBootServicesTableLib
  BaseMemoryLib
  UefiLib
  UefiRuntimeServicesTableLib

[Protocols]

[FeaturePcd]

[Pcd]

[Guids]

[BuildOptions]
   GCC:*_*_IA32_CC_FLAGS = -D__UEFI__ -DLARGEFILE64_SOURCE=1  -w

然后将

zlib|zlib-1.2.6/zlib.inf # zlib-1.2.6 在EKD2的根目录下

放到.dsc 文件 [LibraryClasses]中。 需要链接zlib的时候,在.inf文件的

[LibraryClasses]

中添加 zlib即可。
(4)driver模块的inf文件。例如DiskIo的inf(

MdeModulePkg/Universal/Disk/DiskIoDxe/DiskIoDxe.inf

)

[Defines]
  INF_VERSION                    = 0x00010005
  BASE_NAME                      = DiskIoDxe
  FILE_GUID                        = 6B38F7B4-AD98-40e9-9093-ACA2B5A253C4
  MODULE_TYPE                   = UEFI_DRIVER
  VERSION_STRING               = 1.0
  ENTRY_POINT                    = InitializeDiskIo

#
# The following information is for reference only and not required by the build tools.
#
#  VALID_ARCHITECTURES           = IA32 X64 IPF EBC
#
#  DRIVER_BINDING                =  gDiskIoDriverBinding
#  COMPONENT_NAME                =  gDiskIoComponentName
#  COMPONENT_NAME2               =  gDiskIoComponentName2
#

[Sources]
  ComponentName.c
  DiskIo.h
  DiskIo.c

[Packages]
  MdePkg/MdePkg.dec

[LibraryClasses]
  UefiBootServicesTableLib
  MemoryAllocationLib
  BaseMemoryLib
  BaseLib
  UefiLib
  UefiDriverEntryPoint
  DebugLib

[Protocols]
  gEfiDiskIoProtocolGuid                        ## BY_START
  gEfiBlockIoProtocolGuid                       ## TO_START

现在我们已经扫除了编译UEFI应用的所有障碍。 在下一部分,我们将了解开发UEFI一定用到的系统服务。

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

UEFI 实战(3) 用C++开发UEFI应用

3.1 C/C++的差异

我们知道从源代码到可执行文件分为两个过程编译和链接。  编译C源文件生成的目标文件和编译C++源文件生成的目标文件格式是完全相同的。然后由相同的连接器连接生成可执行代码。 那么我们就可以将重点放在编译阶段。首先来看C语法和C++语法最重要的几个不同点。
1. 编译后的函数名,数组名不同。
    C编译后的附后名字由_和原名字组合成的。而C++编译后的名字要复杂许多,在原名字的基础上要加上参数类型的信息。如果在C++代码中调用C函数和数组,要在函数和数组声明前加上extern “C”. 所以我们会看到在stdio.h等头文件中会有
    #ifdef __cplusplus
    extern “C”{
    #endif
    因为EDK2设计之初没有考虑对C++的支持,所以所有头文件中都没有加入以上代码。
2. 对NULL的定义不同
#ifdef __cplusplus
#define NULL    0
#else
#define NULL    ((void *)0)
#endif
3. 对boolean类型的处理不同
C中基本类型不包含boolean类型,而C++中包含bool类型。 EDK2中 有如下定义typedef unsigned char BOOLEAN;
3.2 解决方案
针对第一点,在C++文件中,我们只需将 C的头文件放入 extern “C”中 即可解决大部分问题。例如
extern “C” {
#include <Protocol/HiiDatabase.h>
#include <Protocol/HiiString.h>
#include <Protocol/Print2.h>
#include <Protocol/Tcp4.h>
}
比较棘手之处在于EDK在编译选项中加入了/FIAutoGen.h 而在AutoGen.h 中包含了#include <Base.h> #include <Uefi.h> #include <Library/PcdLib.h> 三个头文件。 这就造成在C++中这四个头文件没有被extern “C” 包围。
有两种解决办法,
(1) 一是修改编译选项,在.inf文件的[BuildOptions] 使用 == 清除EDK默认选项,例如
[BuildOptions] 
  MSFT:*_*_IA32_CC_FLAGS ==  /nologo /c /WX /GS- /W4 /Gs32768 /D UNICODE /O1ib2 /GL /FIAutoGenLocal.h  /EHs-c- /GR- /GF /Gy /Zi /Gm /D EFI_SPECIFICATION_VERSION=0x0002000A /D TIANO_RELEASE_VERSION=0x00080006 /FAs  /Zc:wchar_t-  /GL- /Od
然后生成AutoGenLocal.h 在AutoGenLocal.h 加入
#ifdef __cplusplus
extern “C”{
#endif
#include “AutoGen.h”
#ifdef __cplusplus
}
#endif
(2)不修改.inf中的[BuildOptions] 。 所有在 <AutoGen.h> <Base.h>  <Uefi.h>   <Library/PcdLib.h>中声明过的函数和数组只在.c文件中使用。
针对第二点
需要 #undef NULL
#define NULL 0
针对第三点
或者将MdePkgIncludeBase.h:42 行改为 #define VERIFY_SIZE_OF(TYPE, Size) extern UINT8 _VerifySizeof##TYPE[((UINTN)(sizeof(TYPE) == (Size))) / ((UINTN)(sizeof(TYPE) == (Size)))]
或者在.inf 中的编译选项里添加/wd4804
目前为止,我猛应该可以类,模板等来开发UEFI应用了。 有些应用中我们会用的全局类实例, 为了支持全局的构造和析构函数,我们先来研究一下windows或linux下的程序是如何支持这些特性的。
3.2 程序启动过程
我们知道源文件要有main函数才能被编译成可执行文件。main是入口函数吗?象printf这样的标准库里的函数是要初始化之后才能被使用的,那么初始化函数在什么地方呢。在C++中声明一个全局对象,这些全局的构造函数是在main之前被执行的,这又是如何实现的呢?
分析vccrtsrc目录下crt0.c我们就会知道问题的答案。我们会看到在__tmainCRTStartup中调用了main。我们来看看__tmainCRTStartup做了哪些工作。
__tmainCRTStartup大致如下
int __tmainCRTStartup(         void )
{
if ( !_heap_init(1) )               /* initialize heap */
   …
if( !_mtinit() )                    /* initialize multi-thread */
   …
if ( _ioinit() < 0 )            /* initialize lowio */
   …
/* get wide cmd line info */
            _tcmdln = (_TSCHAR *)GetCommandLineT();
/* get wide environ info */
            _tenvptr = (_TSCHAR *)GetEnvironmentStringsT();
if ( _tsetargv() < 0 )
    …
if ( _tsetenvp() < 0 )
     …
initret = _cinit(TRUE);                  /* do C data initialize */
mainret = _tmain(__argc, _targv, _tenviron);
 _cexit();
}
可以看到在main之前有大量的初始化工作,我们要关心的是_cinit。 在_cinit中有如下语句
   /*
         * do initializations
         *
 initret = _initterm_e( __xi_a, __xi_z );
 /*
         * do C++ initializations
         */
        _initterm( __xc_a, __xc_z );
再看
static void __cdecl _initterm ( 
        _PVFV * pfbegin,
        _PVFV * pfend
        )
{
        /*
         * walk the table of function pointers from the bottom up, until
         * the end is encountered.  Do not skip the first entry.  The initial
         * value of pfbegin points to the first valid entry.  Do not try to
         * execute what pfend points to.  Only entries before pfend are valid.
         */
        while ( pfbegin < pfend )
        {
            /*
             * if current table entry is non-NULL, call thru it.
             */
            if ( *pfbegin != NULL )
                (**pfbegin)();
            ++pfbegin;
        }
}
可以看出在_initterm中依次执行了从pfbegin开始到pfend结束的数组里的函数。再看 _initterm( __xc_a, __xc_z );中 __xc_a, __xc_z 的声明

extern _CRTALLOC(“.CRT$XIA”) _PIFV __xi_a[];
extern _CRTALLOC(“.CRT$XIZ”) _PIFV __xi_z[];    /* C initializers */
extern _CRTALLOC(“.CRT$XCA”) _PVFV __xc_a[];
extern _CRTALLOC(“.CRT$XCZ”) _PVFV __xc_z[];    /* C++ initializers */
extern _CRTALLOC(“.CRT$XPA”) _PVFV __xp_a[];
extern _CRTALLOC(“.CRT$XPZ”) _PVFV __xp_z[];    /* C pre-terminators */
extern _CRTALLOC(“.CRT$XTA”) _PVFV __xt_a[];
extern _CRTALLOC(“.CRT$XTZ”) _PVFV __xt_z[];    /* C terminators */
我们知道在连接的时候,连接器会将不同文件中名子相同($前的字串)的段合并在一起,并按照$后面的字排序。 编译时编译器遇到全局类实例,会生成一个无参数的函数来调用该实例的构造函数,并将该函数指针放置到CRT$XCU段中。如果该实例有析构函数,则生成一个无参数的函数调用该析构函数,并将该函数通过atexit(…)注册。
_cexit类似。
3.3支持全局构造和析构
明白了以上原理,为了支持全局的构造和析构,我们需要1. 声明__xc_a[]之类的变量。2. 提高atexit()服务。3. 在进入UefiMain时调用_initterm_e( __xi_a, __xi_z );  _initterm( __xc_a, __xc_z ); 退出之前调用_initterm_e( __xp_a, __xp_z );  _initterm( __xt_a, __xt_z );
crt0.cpp

extern “C” {
#pragma section(“.CRTMP$XCA”,long,read)
#pragma section(“.CRTMP$XCZ”,long,read)
#pragma section(“.CRTMP$XIA”,long,read)
#pragma section(“.CRTMP$XIZ”,long,read)

#pragma section(“.CRTMA$XCA”,long,read)
#pragma section(“.CRTMA$XCZ”,long,read)
#pragma section(“.CRTMA$XIA”,long,read)
#pragma section(“.CRTMA$XIZ”,long,read)

#pragma section(“.CRTVT$XCA”,long,read)
#pragma section(“.CRTVT$XCZ”,long,read)

#pragma section(“.CRT$XCA”,long,read)
#pragma section(“.CRT$XCAA”,long,read)
#pragma section(“.CRT$XCB”,long,read)
#pragma section(“.CRT$XCC”,long,read)
#pragma section(“.CRT$XCZ”,long,read)
#pragma section(“.CRT$XDA”,long,read)
#pragma section(“.CRT$XDC”,long,read)
#pragma section(“.CRT$XDZ”,long,read)
#pragma section(“.CRT$XIA”,long,read)
#pragma section(“.CRT$XIAA”,long,read)
#pragma section(“.CRT$XIC”,long,read)
#pragma section(“.CRT$XID”,long,read)
#pragma section(“.CRT$XIY”,long,read)
#pragma section(“.CRT$XIZ”,long,read)
#pragma section(“.CRT$XLA”,long,read)
#pragma section(“.CRT$XLC”,long,read)
#pragma section(“.CRT$XLD”,long,read)
#pragma section(“.CRT$XLZ”,long,read)
#pragma section(“.CRT$XPA”,long,read)
#pragma section(“.CRT$XPX”,long,read)
#pragma section(“.CRT$XPXA”,long,read)
#pragma section(“.CRT$XPZ”,long,read)
#pragma section(“.CRT$XTA”,long,read)
#pragma section(“.CRT$XTB”,long,read)
#pragma section(“.CRT$XTX”,long,read)
#pragma section(“.CRT$XTZ”,long,read)

#pragma section(“.rdata$T”,long,read)
#pragma section(“.rtc$IAA”,long,read)
#pragma section(“.rtc$IZZ”,long,read)
#pragma section(“.rtc$TAA”,long,read)
//#pragma section(“.rtc$TZZ”,long,read) 
}
typedef void (__cdecl *_PVFV)(void);
typedef int  (__cdecl *_PIFV)(void);
typedef void (__cdecl *_PVFI)(int);
#define _CRTALLOC(x) __declspec(allocate(x))   
#undef NULL
#define NULL 0
_CRTALLOC(“.CRT$XIA”) _PIFV __xi_a[] = { NULL };
_CRTALLOC(“.CRT$XIZ”) _PIFV __xi_z[] = { NULL };
_CRTALLOC(“.CRT$XCA”) _PVFV __xc_a[] = { NULL };
_CRTALLOC(“.CRT$XCZ”) _PVFV __xc_z[] = { NULL };
_CRTALLOC(“.CRT$XPA”) _PVFV __xp_a[] = { NULL };
_CRTALLOC(“.CRT$XPZ”) _PVFV __xp_z[] = { NULL };
_CRTALLOC(“.CRT$XTA”) _PVFV __xt_a[] = { NULL };
_CRTALLOC(“.CRT$XTZ”) _PVFV __xt_z[] = { NULL };
#pragma comment(linker, “/merge:.CRT=.rdata”)

_PVFV *atexits  = NULL;
int num_atexit = 0;
int max_atexit =-1;
/** The atexit function registers the function pointed to by func, to be
    called without arguments at normal program termination.
 
     @return   The atexit function returns zero if the registration succeeds,
     nonzero if it fails.
**/
int
atexit(void (*handler)(void))
{   
    if(handler == NULL)
        return 0;
    if(num_atexit >= max_atexit){
        max_atexit += 32;
        _PVFV* old_handler = atexits;
        atexits    = new _PVFV[max_atexit];
        if(atexits == NULL) {
            atexits = old_handler;
            return -1;
        }
        for(int i=0;i<max_atexit-32;i++)
            atexits[i] = old_handler[i];
        delete old_handler;       
    }
    atexits[num_atexit++] = handler;
    return 0;
}
void static _g_finit()
{
    for(int i =num_atexit-1; i>= 0; i–){
        if ( atexits[i] != NULL )
            (*atexits[i])();       
    }
}
#pragma section(“.CRT$XPYZ”, long ,read)
_CRTALLOC(“.CRT$XPYZ”) _PVFV __xp_finitz[] = { _g_finit };

template<typename _PVFV>
void _minitterm (
        _PVFV * pfbegin,
        _PVFV * pfend
        )
{
       /*
         * walk the table of function pointers from the bottom up, until
         * the end is encountered.  Do not skip the first entry.  The initial
         * value of pfbegin points to the first valid entry.  Do not try to
         * execute what pfend points to.  Only entries before pfend are valid.
         */
        while ( pfbegin < pfend )
        {
            /*
             * if current table entry is non-NULL, call thru it.
             */
            if ( *pfbegin != NULL )
                (**pfbegin)();
            ++pfbegin;
        }
}

void _Uefi_cpp_init()
{
    _minitterm(__xc_a,__xc_z);
    _minitterm(__xi_a,__xi_z);
}

void _Uefi_cpp_finit()
{
    _minitterm(__xp_a,__xp_z);
    _minitterm(__xt_a,__xt_z);

3.4支持new和delete
C++遇到new operator时, 首先调用operator new 函数 获得内存, 然后在获得的内存上调用构造函数,遇到 delete operator则首先调用析构函数,然后调用operator delete函数释放内存。
      遇到new operator[]时, 首先调用operator new []函数 获得内存, 然后记录数组的大小,并在获得的内存上依次调用构造函数,遇到 delete operator[]则首先根据内存记录的数组大小依次调用析构函数,然后调用operator delete[]函数释放内存。
如此我们只需提供 void *  operator new( size_t cb ); void *  operator new[]( size_t cb ); void operator delete( void * p ); void operator delete[]( void * p ); 函数即可。
stduefi.cpp

#ifdef __cplusplus
extern “C”{
#endif

/** The malloc function allocates space for an object whose size is specified
by size and whose value is indeterminate.

This implementation uses the UEFI memory allocation boot services to get a
region of memory that is 8-byte aligned and of the specified size.  The
region is allocated with type EfiLoaderData.

@param  size    Size, in bytes, of the region to allocate.

@return   NULL is returned if the space could not be allocated and errno
contains the cause.  Otherwise, a pointer to an 8-byte aligned
region of the requested size is returned.<BR>
If NULL is returned, errno may contain:
– EINVAL: Requested Size is zero.
– ENOMEM: Memory could not be allocated.
**/
__inline__ void * malloc(size_t Size)
{
    void       *RetVal;
    EFI_STATUS  Status;

    if( Size == 0) {
        //errno = EINVAL;   // Make errno diffenent, just in case of a lingering ENOMEM.
        return NULL;
    }

    Status = gBS->AllocatePool( EfiLoaderData, (UINTN)Size, &RetVal);
    if( Status != EFI_SUCCESS) {
        RetVal  = NULL;
        //errno   = ENOMEM;
    }
    return RetVal;
}

/** The free function causes the space pointed to by Ptr to be deallocated,
that is, made available for further allocation.

If Ptr is a null pointer, no action occurs.  Otherwise, if the argument
does not match a pointer earlier returned by the calloc, malloc, or realloc
function, or if the space has been deallocated by a call to free or
realloc, the behavior is undefined.

@param  Ptr     Pointer to a previously allocated region of memory to be freed.

**/
__inline__ void free(void *Ptr)
{
    (void) gBS->FreePool (Ptr);
}

#ifdef __cplusplus
}// end of extern “C”下载示例
#endif
__inline__ void *  operator new( size_t cb )
{
    void *res;
    res = malloc(cb);
    return res;
}

__inline__ void *  operator new[]( size_t cb )
{
    void *res = operator new(cb);
    return res;
}

__inline__ void operator delete( void * p )
{
    free( p );
}

__inline__ void operator delete[]( void * p )
{
    operator delete(p);
}

3.5支持STL
我们不能直接使用系统的STL,因为系统的STL会调用自己的std函数。我们只需将STL从系统目录中剥离出来,然后使用UEFI中的StdPkg,再补上缺失的函数(C++run time库中的一些函数)即可。

main.cpp

void _Uefi_cpp_init();
void _Uefi_cpp_finit();

EFI_STATUS
EFIAPI
UefiMain (
          IN EFI_HANDLE        ImageHandle,
          IN EFI_SYSTEM_TABLE  *SystemTable
          )
{
    _Uefi_cpp_init();

    //! Do your work here

    _Uefi_cpp_finit();
}

下载示例

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

UEFI 实战(4) protocol

什么是protocol
从字面意思上看,protocol是server和client之间的一种约定,双方根据这种约定互通信息。这里的server和client是一种广义的称呼,提供服务的称为server,使用服务的称为client。 TCP是一种protocol, client(应用程序)通过一组函数来压包和解包,压包和解包是server提供的服务。COM也是一种protocol,client通过CoCreateInstance(…)和GUID获得指向COM对象的指针,然后使用该指针获得COM对象提供的服务, GUID标示了这个COM对象。现在我们对protocol有了概念上的理解,那么具体到UEFI里,protocol是什么样子呢? 如何标示一个protocol?如何得到protocol对应的对象?…容我慢慢道来.
在讲protocol什么样子之前,还要插几句C与C++的区别。我们知道UEFI是用C来开发的,C是面向过程的一种语言。而管理和使用UEFI众多的protocol完全使用面向过程的思想会使程序变得复杂。protocol作为一种对象来设计管理会比较直观。因而UEFI中的Protocol引入了面向对象的思想,用struct来模拟class, Protocol用struct来实现,用函数指针(Protocol的成员变量)模拟成员函数,此种函数的第一参数必须是指向Protocol的指针,用来模拟this指针。
Protocol的摸样
以EFI_DISKIO_PROTOCOL 来看看Protocol的样子。 

MdePkg/Include/Protocol/BlockIo.h

:220

///
///  This protocol provides control over block devices.///struct _EFI_BLOCK_IO_PROTOCOL {
  ///
  /// The revision to which the block IO interface adheres. All future
  /// revisions must be backwards compatible. If a future version is not
  /// back wards compatible, it is not the same GUID.
  ///  UINT64              Revision;
  ///
  /// Pointer to the EFI_BLOCK_IO_MEDIA data for this device.
  ///  EFI_BLOCK_IO_MEDIA  *Media;

  EFI_BLOCK_RESET     Reset;
  EFI_BLOCK_READ      ReadBlocks;
  EFI_BLOCK_WRITE     WriteBlocks;
  EFI_BLOCK_FLUSH     FlushBlocks;

};

extern EFI_GUID gEfiBlockIoProtocolGuid;

MdePkg/Include/Protocol/BlockIo.h

:220

#define EFI_BLOCK_IO_PROTOCOL_GUID 
  { 
    0x964e5b21, 0x6459, 0x11d2, {0x8e, 0x39, 0x0, 0xa0, 0xc9, 0x69, 0x72, 0x3b } 
  }

typedef struct _EFI_BLOCK_IO_PROTOCOL  EFI_BLOCK_IO_PROTOCOL;

EFI_BLOCK_IO_PROTOCOL 有两个成员变量,四个成员函数(当然从C的角度来看,“成员函数”叫法不准确,它实际上也是一个成员变量,只是这个变量是函数指针).  gEfiBlockIoProtocolGuid({0x964e5b21, 0x6459, 0x11d2, {0x8e, 0x39, 0x0, 0xa0, 0xc9, 0x69, 0x72, 0x3b })标示了EFI_BLOCK_IO_PROTOCOL 。

来看成员函数的声明


/**
  Read BufferSize bytes from Lba into Buffer.

  @param  This       Indicates a pointer to the calling context.
  @param  MediaId    Id of the media, changes every time the media is replaced.
  @param  Lba        The starting Logical Block Address to read from
  @param  BufferSize Size of Buffer, must be a multiple of device block size.
  @param  Buffer     A pointer to the destination buffer for the data. The caller is
                     responsible for either having implicit or explicit ownership of the buffer.

  @retval EFI_SUCCESS           The data was read correctly from the device.
  @retval EFI_DEVICE_ERROR      The device reported an error while performing the read.
  @retval EFI_NO_MEDIA          There is no media in the device.
  @retval EFI_MEDIA_CHANGED     The MediaId does not matched the current device.
  @retval EFI_BAD_BUFFER_SIZE   The Buffer was not a multiple of the block size of the device.
  @retval EFI_INVALID_PARAMETER The read request contains LBAs that are not valid,
                                or the buffer is not on proper alignment.

**/
typedef
EFI_STATUS
(EFIAPI *EFI_BLOCK_READ)(
  IN EFI_BLOCK_IO_PROTOCOL          *This,
  IN UINT32                         MediaId,
  IN EFI_LBA                        Lba,
  IN UINTN                          BufferSize,
  OUT VOID                          *Buffer
  );

EFI_BLOCK_READ具体用法我们先不看,我们来看它的第一个参数,指向EFI_BLOCK_IO_PROTOCOL  对象自己的this指针,这是成员函数区别于一般函数的重要特征。

如何使用Protocol       
使用Protocol之前,我们要弄清楚Protocol位于什么地方。首先我们要来认识一下EFI_HANDLE,

///
/// A collection of related interfaces.///typedef VOID                      *EFI_HANDLE;

EFI_HANDLE是指向某种对象的指针,UEFI用它来表示某个对象。 UEFI扫描总线后,会为每个设备建立一个Controller对象,用于控制设备,所有该设备的驱动以protocol的形式安装到这个controller中,这个Controller就是一个EFI_HANDLE对象。 当我们将一个.efi文件加载到内存中,UEFI也会为该文件建立一个Image对象(此Image非图像的意识), 这个Image对象也是一个EFI_HANDLE对象。 在UEFI内部,EFI_HANDLE被理解为IHANDLE

///
/// IHANDLE – contains a list of protocol handles///typedef struct {
  UINTN               Signature;
  /// All handles list of IHANDLE
  LIST_ENTRY          AllHandles;
  /// List of PROTOCOL_INTERFACE’s for this handle
  LIST_ENTRY          Protocols;
  UINTN               LocateRequest;
  /// The Handle Database Key value when this handle was last created or modified
  UINT64              Key;
} IHANDLE;

每个IHANDLE中都有一个Protocols链表,存放属于自己的protocol。所有的IHANDLE通过AllHandles链接起来。
要使用Protocol,首先要找到protocol对象,可以通过BootServices的OpenProtocol(…), HandleProtocl(…), LocateProtocol(…)获得。

typedef
/**
  Queries a handle to determine if it supports a specified protocol. If the protocol is supported by the
  handle, it opens the protocol on behalf of the calling agent.
  @param  Handle                The handle for the protocol interface that is being opened.
  @param  Protocol              The published unique identifier of the protocol.
  @param  Interface             Supplies the address where a pointer to the corresponding Protocol
                                        Interface is returned.
  @param  AgentHandle        The handle of the agent that is opening the protocol interface
                                        specified by Protocol and Interface.
  @param  ControllerHandle    If the agent that is opening a protocol is a driver that follows the
                                        UEFI Driver Model, then this parameter is the controller handle
                                        that requires the protocol interface. If the agent does not follow
                                        the UEFI Driver Model, then this parameter is optional and may
                                        be NULL.
  @param  Attributes            The open mode of the protocol interface specified by Handle
                                        and Protocol.
  @retval EFI_SUCCESS         An item was added to the open list for the protocol interface, and the
                                        protocol interface was returned in Interface.
  @retval EFI_UNSUPPORTED       Handle does not support Protocol.
  @retval EFI_INVALID_PARAMETER One or more parameters are invalid.
  @retval EFI_ACCESS_DENIED     Required attributes can’t be supported in current environment.
  @retval EFI_ALREADY_STARTED   Item on the open list already has requierd attributes whose agent
                                                handle is the same as AgentHandle.
**/

EFI_STATUS

(EFIAPI *EFI_OPEN_PROTOCOL)(
  IN  EFI_HANDLE                Handle,
  IN  EFI_GUID                   *Protocol,
  OUT VOID                       **Interface, OPTIONAL
  IN  EFI_HANDLE                AgentHandle,
  IN  EFI_HANDLE                ControllerHandle,
  IN  UINT32                      Attributes
  );

Handle是Protocol的提供者,如果Handle的Protocols链表中有该Potocol,Protocol对象的指针写到*Interface,并返回EFI_SUCCESS;否则 返回EFI_UNSUPPORTED 。
如果在驱动中调用OpenProtocol(), AgentHandle是拥有该EFI_DRIVER_BINDING_PROTOCOL对象的Handle;ControllerHandle是拥有该驱动的Controller。
如果调用OpenProtocol的是应用程序,那么AgentHandle是该应用对应的Handle,也就main函数的第一个参数。 ControllerHandle此时可以忽略。

Attributes可以取以下5种值。
#define EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL   0x00000001
#define EFI_OPEN_PROTOCOL_GET_PROTOCOL             0x00000002
#define EFI_OPEN_PROTOCOL_TEST_PROTOCOL           0x00000004
#define EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER  0x00000008
#define EFI_OPEN_PROTOCOL_BY_DRIVER                   0x00000010
#define EFI_OPEN_PROTOCOL_EXCLUSIVE                   0x00000020



HandleProtocol是OpenProtocol的简化版,因为大部分情况下我们都不需要关心AgentHandle,ControllerHandle和Attributes。

EFI_STATUS
EFIAPI
CoreHandleProtocol (
  IN EFI_HANDLE       UserHandle,
  IN EFI_GUID         *Protocol,
  OUT VOID            **Interface
  )
{
  return CoreOpenProtocol (
          UserHandle,
          Protocol,
          Interface,
          gDxeCoreImageHandle,
          NULL,
          EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL
          );
}

LocateProtocol(…)是从内核中找出指定Protocol的第一个实例。

typedef
EFI_STATUS
LocateProtocol (
IN EFI_GUID *Protocol,
IN VOID       *Registration OPTIONAL,
OUT VOID     **Interface
);

UEFI内核中某个Protocol的实例可能不止一个,例如每个硬盘及每个分区都有一个EFI_DISK_IO_PROTOCOL实例。LocateProtocol顺序搜索HANDLE链表,返回找到的第一个该Protocol的实例。
我们可以用BootServices提供的其它函数处理HANDLE和Protocol。

typedef
EFI_STATUS
LocateHandleBuffer (
IN EFI_LOCATE_SEARCH_TYPE SearchType,
IN EFI_GUID                         *Protocol OPTIONAL,
IN VOID

                              

 *SearchKey OPTIONAL,
IN OUT UINTN                       *NoHandles,
OUT EFI_HANDLE                   **Buffer
);

可以获得所有支持指定Protocol的HANDLE,SearchType 有三种:AllHandles(查找所有HANDLE), ByRegisterNotify, ByProtocol(查找支持指定Protocol的HANDLE)。NoHandles是找到的HANDLE的数量, Buffer数组由UEFI复杂分配,由用户负责释放。

typedef
EFI_STATUS
LocateHandle (
IN EFI_LOCATE_SEARCH_TYPE SearchType,
IN EFI_GUID                        *Protocol OPTIONAL,
IN VOID                              *SearchKey OPTIONAL,
IN OUT UINTN                      *BufferSize,
OUT EFI_HANDLE                  *Buffer
);

与LocateHandleBuffer相似,只是用户负责分配和释放Buffer数组。

typedef
EFI_STATUS
ProtocolsPerHandle (
IN EFI_HANDLE Handle,
OUT EFI_GUID  ***ProtocolBuffer,
OUT UINTN      *ProtocolBufferCount
);

获得指定Handle所支持的所有Protocol, UEFI负责分配内存给ProtocolBuffer,用户负责释放该内存。

typedef
EFI_STATUS
(EFIAPI *EFI_OPEN_PROTOCOL_INFORMATION) (
IN EFI_HANDLE Handle,
IN EFI_GUID    *Protocol,
OUT EFI_OPEN_PROTOCOL_INFORMATION_ENTRY **EntryBuffer,
OUT UINTN     *EntryCount
);

typedef struct {
  EFI_HANDLE  AgentHandle;
  EFI_HANDLE  ControllerHandle;
  UINT32        Attributes;
  UINT32        OpenCount;
} EFI_OPEN_PROTOCOL_INFORMATION_ENTRY;

OpenProtocolInformation()获得指定Handle中指定Protocol的打开信息。
SPEC2.3.1第165页有很好的例子演示了怎么打开一个Protocol, 

EFI_BOOT_SERVICES *gBS;
EFI_HANDLE ImageHandle;
EFI_DRIVER_BINDING_PROTOCOL *This;
IN EFI_HANDLE ControllerHandle,
extern EFI_GUID gEfiXyzIoProtocol;
EFI_XYZ_IO_PROTOCOL *XyzIo;
EFI_STATUS Status;

Status = gBS->OpenProtocol (
ControllerHandle,
&gEfiXyzIoProtocol,
&XyzIo,
ImageHandle,
NULL,
EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL
);

Status = gBS->OpenProtocol (
ControllerHandle,
&gEfiXyzIoProtocol,
&XyzIo,
This->DriverBindingHandle,
ControllerHandle,
EFI_OPEN_PROTOCOL_GET_PROTOCOL
);

打开Protocol之后就可以使用了,最后要通过CloseProtocol关闭打开的Protocol。

typedef
EFI_STATUS
(EFIAPI *EFI_CLOSE_PROTOCOL) (
IN EFI_HANDLE Handle,
IN EFI_GUID *Protocol,
IN EFI_HANDLE AgentHandle,
IN EFI_HANDLE ControllerHandle
);

通过HandleProtocol和LocateProtocol打开的Protocol因为没有指定AgentHandle,所以无法关闭。如果一定要去关闭它,要调用OpenProtocolInformation()获得AgentHandle和ControllerHandle,然后关闭它。
下面看一个完整的例子,用EFI_DISK_IO_PROTOCOL读取GPT硬盘的分区表

#include <Uefi.h> 
#include <Base.h> 
#include <Library/UefiLib.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/PrintLib.h>
#include <Protocol/DiskIo.h> 
#include <Protocol/BlockIo.h> 
#include <Protocol/DevicePath.h>    
#include <Uefi/UefiGpt.h>
#include <Library/DevicePathLib.h>

EFI_STATUS
EFIAPI
UefiMain(
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
        EFI_STATUS                              Status;
        UINTN                                       HandleIndex, HandleCount;
        EFI_HANDLE                               *DiskControllerHandles = NULL;
        EFI_DISK_IO_PROTOCOL               *DiskIo;

        /*找到所有提供 EFI_DISK_IO_PROTOCOL 的Controller  */
        Status = gBS->LocateHandleBuffer(
                        ByProtocol,
                        &gEfiDiskIoProtocolGuid,
                        NULL,
                        &HandleCount,
                        &DiskControllerHandles);

        if (!EFI_ERROR(Status)) {
                CHAR8 gptHeaderBuf[512];                EFI_PARTITION_TABLE_HEADER* gptHeader = (EFI_PARTITION_TABLE_HEADER*
)gpHeaderBuf;
                for (HandleIndex = 0; HandleIndex < HandleCount; HandleIndex++) {
                       /*打开EFI_DISK_IO_PROTOCOL  */ 
                       Status = gBS->HandleProtocol(
                                        DiskControllerHandles[HandleIndex],
                                        &gEfiDiskIoProtocolGuid,
                                        (VOID**)&DiskIo);

                        if (!EFI_ERROR(Status)){
                                {
                                        EFI_DEVICE_PATH_PROTOCOL                 *DiskDevicePath;
                                        EFI_DEVICE_PATH_TO_TEXT_PROTOCOL   *Device2TextProtocol = 0;
                                        CHAR16*                                             TextDevicePath = 0;
                                          /*1. 打开EFI_DEVICE_PATH_PROTOCOL  */  
                                        Status = gBS->OpenProtocol(
                                                        DiskControllerHandles[HandleIndex],
                                                        &gEfiDevicePathProtocolGuid,
                                                        (VOID**)&DiskDevicePath,
                                                        ImageHandle,
                                                        NULL,
                                                        EFI_OPEN_PROTOCOL_GET_PROTOCOL
                                                        );
                                        if(!EFI_ERROR(Status)){
                                                if(Device2TextProtocol == 0)
                                                        Status = gBS->LocateProtocol(
                                                                        &gEfiDevicePathToTextProtocolGuid,
                                                                        NULL,
                                                                        (VOID**)&Device2TextProtocol
                                                                        );
                                                /*2. 使用 EFI_DEVICE_PATH_PROTOCOL  得到文本格式的Device Path  */  
                                                TextDevicePath = Device2TextProtocol->ConvertDevicePathToText(DiskDevicePath, TRUE, TRUE);
                                                Print(L”%sn”, TextDevicePath);
                                                if(TextDevicePath)gBS->FreePool(TextDevicePath);
                                                /*3. 关闭 EFI_DEVICE_PATH_PROTOCO */   
                                                Status = gBS->CloseProtocol(
                                                                DiskControllerHandles[HandleIndex],
                                                                &gEfiDevicePathProtocolGuid,
                                                                ImageHandle,
                                                                );
                                        }
                                }
                                {
                                        EFI_BLOCK_IO_PROTOCOL* BlockIo = *(EFI_BLOCK_IO_PROTOCOL**) (DiskIo + 1);
                                        EFI_BLOCK_IO_MEDIA* Media = BlockIo->Media;
                                        /*读1号扇区。  */   
                                        Status = DiskIo->ReadDisk(DiskIo, Media->MediaId, 512, 512, gptHeader);
                                        /*检查GPT标志。  */    
                                        if((!EFI_ERROR(Status)) &&( gptHeader -> Header.Signature == 0x5452415020494645)){
                                                UINT32 CRCsum;
                                                UINT32 GPTHeaderCRCsum =  (gptHeader->Header.CRC32);
                                                gptHeader->Header.CRC32 = 0;
                                                gBS -> CalculateCrc32(gptHeader , (gptHeader->Header.HeaderSize), &CRCsum);
                                                if(GPTHeaderCRCsum == CRCsum){
                                                // Find out a GPT Header
                                                }

                                        }

                                }

                        }

                }
                gBS->FreePool(DiskControllerHandles);
        }
}



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

UEFI driver框架
上一节我们讲了服务型驱动,它有如下几个特点:
1. 在Image的入口函数中执行安装。
2. 服务型驱动不需要驱动特定硬件,可以安装到任意controller上。
3. 没有提供卸载函数。
一个真正的驱动程序,在安装时首先要找到对应的硬件设备(在UEFI中是要找到对应的Controller), 然后执行安装操作,将驱动程序安装到硬件设备的controller上。 有时候我们还需要卸载驱动,更新驱动(先卸载旧的驱动,然后安装新的驱动)。 有时候安装操作可能需要执行多次,例如:第一次安装时发现设备没有准备好,或者所依赖的某个Protocol没有安装,就需要退出安装,执行其他操作,然后进行第二次安装。
那么我们可以总结出一个完整的驱动程序框架需要三部分:
1.  Findout() 找出对应的硬件设备
2.  Install()  安装驱动到指定的硬件设备 或者说 Start() 启动硬件设备
3. Uninstall()从硬件设备中卸载驱动  或者说 Stop() 停止硬件设备。
另外很重要的一点是框架必须支持多次安装。 上一节我们实现的驱动是不能多次安装的(在入口函数中执行安装),如果首次安装失败例如InstallProtocolInterface(…)返回错误,我们只能unload 驱动文件(image),从新loadImage。
我们来看UEFI驱动框架是如何实现这几点的。
在UEFI驱动的入口函数中,安装EFI Driver Binding Protocol到某个Handle(大部分情况下是自身即ImageHandle, 有时也会安装到其它Handle上), 这个Driver Binding Protocol实例会常驻内存,用于驱动的安装和卸载。使用DriverBindingProtocol使得我们可以多次操作(查找设备,安装卸载)驱动.
在Driver Binding Protocol中实现了框架的三个部分的接口。下面是DriverBindingProtocol的声明:

///// This protocol provides the services required to determine if a driver supports a given controller. /// If a controller is supported, then it also provides routines to start and stop the controller.///struct _EFI_DRIVER_BINDING_PROTOCOL {
  EFI_DRIVER_BINDING_SUPPORTED  Supported;
  EFI_DRIVER_BINDING_START      Start;
  EFI_DRIVER_BINDING_STOP       Stop;

  ///
  /// The version number of the UEFI driver that produced the
  /// EFI_DRIVER_BINDING_PROTOCOL. This field is used by
  /// the EFI boot service ConnectController() to determine
  /// the order that driver’s Supported() service will be used when
  /// a controller needs to be started. EFI Driver Binding Protocol
  /// instances with higher Version values will be used before ones
  /// with lower Version values. The Version values of 0x0-
  /// 0x0f and 0xfffffff0-0xffffffff are reserved for
  /// platform/OEM specific drivers. The Version values of 0x10-
  /// 0xffffffef are reserved for IHV-developed drivers.
  ///  UINT32                        Version;

  ///
  /// The image handle of the UEFI driver that produced this instance
  /// of the EFI_DRIVER_BINDING_PROTOCOL.
  ///  EFI_HANDLE                    ImageHandle;

  ///
  /// The handle on which this instance of the
  /// EFI_DRIVER_BINDING_PROTOCOL is installed. In most
  /// cases, this is the same handle as ImageHandle. However, for
  /// UEFI drivers that produce more than one instance of the
  /// EFI_DRIVER_BINDING_PROTOCOL, this value may not be
  /// the same as ImageHandle.
  ///  EFI_HANDLE                    DriverBindingHandle;
};

核心是Support,Start,Stop三个成员函数,对应我们总结的三个部分。
首先看Support函数,简单来讲,如果ControllerHandle是我们要找的Controoler,该函数返回EFI_SUCCESS, 否则返回EFI_UNSUPPORTED、 EFI_ACCESS_DENIED或EFI_ALREADY_STARTED等等。

typedef
EFI_STATUS
(EFIAPI *EFI_DRIVER_BINDING_PROTOCOL_SUPPORTED) (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE ControllerHandle,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL  // Device driver 忽略该参数,
                                                                                           //Bus Driver(例如MdeModulePkg/Bus/Pci/PciBusDxe/PciBus.c)才会使用该参数。
);

再看Start函数,Start()用来启动硬件设备,在函数中最重要的事情是调用InstallProtocolInterface()或者InstallMultipleProtocolInterfaces()在ControllerHandle上安装驱动Protocol。

typedef
EFI_STATUS
(EFIAPI *EFI_DRIVER_BINDING_PROTOCOL_START) (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE ControllerHandle,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL
);

Stop函数,用于卸载驱动(调用UninstallProtocolInterface()或UninstallMultipleProtocolInterfaces()从ControllerHandle卸载驱动协议),并停止硬件设备。

typedef
EFI_STATUS
(EFIAPI *EFI_DRIVER_BINDING_PROTOCOL_STOP) (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE ControllerHandle,
IN UINTN NumberOfChildren,
IN EFI_HANDLE *ChildHandleBuffer OPTIONAL
);

对Device Driver来讲,NumberOfChildren 为0, ChildHandleBuffer 为NULL。 对Bus Driver来讲,如果NumberOfChildren不为零,那么ChildHandleBuffer中的子节点都要被释放。我们分别研究了驱动框架的三个部分,这三个部分是如何联系起来的呢?看下面的代码(MdeModulePkg/Core/Dxe/Hand/DriverSupport.c:CoreConnectSingleController),可以大致了解UEFI驱动程序框架是如何工作的。

do {

    //
    // Loop through the sorted Driver Binding Protocol Instances in order, and see if
    // any of the Driver Binding Protocols support the controller specified by
    // ControllerHandle.
    //    DriverBinding = NULL;
    DriverFound = FALSE;
    for (Index = 0; (Index < NumberOfSortedDriverBindingProtocols) && !DriverFound; Index++) {
      if (SortedDriverBindingProtocols[Index] != NULL) {
        DriverBinding = SortedDriverBindingProtocols[Index];
        PERF_START (DriverBinding->DriverBindingHandle, “DB:Support:”, NULL, 0);
        Status = DriverBinding->Supported(
                                  DriverBinding,
                                  ControllerHandle,
                                  RemainingDevicePath
                                  );
        PERF_END (DriverBinding->DriverBindingHandle, “DB:Support:”, NULL, 0);
        if (!EFI_ERROR (Status)) {
          SortedDriverBindingProtocols[Index] = NULL;
          DriverFound = TRUE;

          //
          // A driver was found that supports ControllerHandle, so attempt to start the driver
          // on ControllerHandle.
          //          PERF_START (DriverBinding->DriverBindingHandle, “DB:Start:”, NULL, 0);
          Status = DriverBinding->Start (
                                    DriverBinding,
                                    ControllerHandle,
                                    RemainingDevicePath
                                    );
          PERF_END (DriverBinding->DriverBindingHandle, “DB:Start:”, NULL, 0);

          if (!EFI_ERROR (Status)) {
            //
            // The driver was successfully started on ControllerHandle, so set a flag
            //            OneStarted = TRUE;
          }
        }
      }
    }
  } while (DriverFound);

CoreConnectSingleController(IN  EFI_HANDLE ControllerHandle,  IN  EFI_HANDLE *ContextDriverImageHandles OPTIONAL,  IN  EFI_DEVICE_PATH_PROTOCOL  *RemainingDevicePath OPTIONAL)用于为ControllerHandle安装驱动。如果 ContextDriverImageHandles为空, 则遍历系统中的所有DriverBindingProtocol,否则就只遍历指定的DriverBindingProtocol。SortedDriverBindingProtocols[]存放了需要测试的DriverBindingProtocol, 对于每一个需要测试的DriverBindingProtocol,首先调用DriverBinding->Supported(…)测试该DriverBindingProtocol是否支持ControllerHandle, 如果Supported函数返回EFI_SUCCESS,则调用DriverBinding->Start(…)向ControllerHandle安装驱动,启动设备。
CoreDisconnectController(
  IN  EFI_HANDLE  ControllerHandle,
  IN  EFI_HANDLE  DriverImageHandle  OPTIONAL,
  IN  EFI_HANDLE  ChildHandle        OPTIONAL
  ) 用于卸载掉ControllerHandle上指定的驱动(若DriverImageHandle 则卸载掉ControllerHandle上的所有驱动)。
看MdeModulePkg/Core/Dxe/Hand/DriverSupport.c:876

 if (ChildHandle == NULL || ChildHandleValid) {
        ChildrenToStop = 0;
        Status = EFI_SUCCESS;
        if (ChildBufferCount > 0) {
          if (ChildHandle != NULL) {
            ChildrenToStop = 1;
            Status = DriverBinding->Stop (DriverBinding, ControllerHandle, ChildrenToStop, &ChildHandle);
          } else {
            ChildrenToStop = ChildBufferCount;
            Status = DriverBinding->Stop (DriverBinding, ControllerHandle, ChildrenToStop, ChildBuffer);
          }
        }
        if (!EFI_ERROR (Status) && ((ChildHandle == NULL) || (ChildBufferCount == ChildrenToStop))) {
          Status = DriverBinding->Stop (DriverBinding, ControllerHandle, 0, NULL);
        }
        if (!EFI_ERROR (Status)) {
          StopCount++;
        }
      }



了解了各个部分的细节后,我们在看一遍加载Driver的整个过程。
首先在Shell中使用命令Load 将Driver文件加载到内存, Load后UEFI会调用gBS->StartImage(…) 执行DriverImage的入口函数, 在入口函数里Driver Binding Protocol被加载到Handle上(Driver Image handle 或者其它的Controller handle),然后UEFI会遍历所有的Controller,调用Driver Binding Protocol的Supported 函数测试这个Driver是否支持该Controller,如果支持则调用Start()安装驱动。


EFI Component Name Protocol


编写Driver
驱动分为两部分,一部分是硬件相关的部分,这部分是驱动的内容,用于驱动硬件设备,为用户提供服务,以协议的形式出现,例如DiskIo,BlockIo。 另一部分驱动的框架部分,需要实现Driver Binding Protocol,主要是其三个接口(Supported, Start, Stop),这部分用于驱动的安装与卸载。硬件相关部分不是本节讲述的重点。 
本节主要讲述Device Driver如何实现框架部分。
Spec中详细描述了如何编写Supported, Start, Stop 三个函数,下面的斜体内容翻译至Spec2.3
Supported函数要点(Spec2.3:339)

1. 忽略参数 RemainingDevicePath
2. 使用函数OpenProtocol()打开所有需要的Protocols。 标准的驱动要用EFI_OPEN_PROTOCOL_BY_DRIVER属性打开Protocol。 如果要独占某个Protocol,首先要关闭所有使用该Protocol的其它驱动,然后用属性EFI_OPEN_PROTOCOL_BY_DRIVER | EFI_OPEN_PROTOCOL_EXCLUSIVE打开Protocol。
3. 如果(2)中OpenProtocol()返回错误,调用CloseProtcol()关闭所有已经打开的Protocol并返回错误代码。
4. 所需的所有Protocols成功打开,则测试这个Driver是否支持此Controller。有时使用这些Protocols足以完成测试,有时还需要此Controller的其它特征。如果任意一项测试失败,则用CloseProtocol()关闭所有打开的Protocol,返回EFI_UNSUPPORTED.
5. 测试成功,调用CloseProtocol()关闭所有已经打开的Protocols。
6. 返回 EFI_SUCCESS。

Start函数要点(spec2.3:345)

1. 忽略参数 RemainingDevicePath
2. 使用函数OpenProtocol()打开所有需要的Protocols。 标准的驱动要用EFI_OPEN_PROTOCOL_BY_DRIVER属性打开Protocol。 如果要独占某个Protocol,首先要关闭所有使用该Protocol的其它驱动,然后用属性EFI_OPEN_PROTOCOL_BY_DRIVER | EFI_OPEN_PROTOCOL_EXCLUSIVE打开Protocol。
3. 如果(2)中OpenProtocol()返回错误,调用CloseProtcol()关闭所有已经打开的Protocol并返回错误代码。
4. 初始化ControllerHandle所指的设备。如果有错误,则关闭所有已打开的Protocols并返回 EFI_DEVICE_ERROR。
5. 分配并初始化要用到的数据结构,这些数据结构包括驱动Protocols及其它相关的私有数据结构。如果分配资源时发生错误,则关闭所有已打开的Protocols,释放已经得到的资源,返回EFI_OUT_OF_RESOURCES。
6. 用InstallMultipleProtocolInterfaces()安装驱动协议到ControllerHandle。如果有错误发生,则关闭所有已打开的Protocols,并返回错误代码。
7. 返回EFI_SUCESS。

Stop函数要点(spec2.3:)

1. 用UninstallMultipleProtocolInterfaces() Uninstall 所安装的Protocols。
2. 关闭所有已打开的Protocols。
3. 释放所有已申请的资源。



下面我们以AC97 控制器驱动为例,介绍如何编写Device driver。南桥芯片中集成的AC97控制器是一种PCI设备,在开始写驱动之前让我们补充一点PCI设备驱动及AC97声卡的背景知识。 
首先让我们建立实验环境, 在QEMU中选择开启 Audio ,类型选择为Intel AC97.
PCI设备及PciIo
每个PCI设备都有三种地址空间:配置空间,IO空间和内存空间。
系统初始化时系统会初始化每个PCI设备的配置空间寄存器。配置地址空间大小为256字节,前64字节是标准的,后面的寄存器由设备自定义用途。

0x0
0x1
0x2
0x3
0x4
0x5
0x6
0x7
0x8
0x9
0xa
0xb
0xc
0xd
0xe
0xf
0x00
Vendor ID
Device ID
Command Reg.
Status Reg.
Revision ID
Class Code
Cache
Line
Latency
Timer
Header
Type
BIST
0x10
Base Address 0
Base Address 1
Base Address 2
Base Address 3
0x20
Base Address 4
Base Address 5
Card Bus CIS pointer
Subsytem Vendor ID
Subsystem Device ID
0x30
Expansion ROM Base Address
IRQ Line
IRQ Pin
Min_Gnt
Max_lat



例如Qemu中AC97的配置空间内容如下

PciRoot(0x0)/Pci(0x4,0x0)
UINT16  VendorId :8086
UINT16  DeviceId :2415
UINT16  Command  :7
UINT16  Status   :280
UINT8   RevisionID : 1
UINT8   ClassCode[2] : 4
UINT8   ClassCode[1] : 1
UINT8   ClassCode[0] : 0
UINT8   CacheLineSize :0
UINT8   LatencyTimer : 0
UINT8   HeaderType :   0
UINT8   BIST :         0
Bar[0] :  C001   
Bar[1] :  C401   
Bar[2] :  0
Bar[3] :  0   
Bar[4] :  0   
Bar[5] :  0    



PCI设备中的IO和内存空间被划分为1~6个互补重叠的子空间,每个子空间用于完成一组相对独立的子功能。Base Address 0 ~5 表示子空间的基地址(物理地址)。
对设备的操作主要是通过对子空间的读写来实现的。 UEFI提供了EFI_PCI_IO_PROTOCOL来操作PCI设备。 

///
/// The EFI_PCI_IO_PROTOCOL provides the basic Memory, I/O, PCI configuration,/// and DMA interfaces used to abstract accesses to PCI controllers./// There is one EFI_PCI_IO_PROTOCOL instance for each PCI controller on a PCI bus./// A device driver that wishes to manage a PCI controller in a system will have to/// retrieve the EFI_PCI_IO_PROTOCOL instance that is associated with the PCI controller.///struct _EFI_PCI_IO_PROTOCOL {
  EFI_PCI_IO_PROTOCOL_POLL_IO_MEM         PollMem;
  EFI_PCI_IO_PROTOCOL_POLL_IO_MEM         PollIo;
  EFI_PCI_IO_PROTOCOL_ACCESS              Mem;
  EFI_PCI_IO_PROTOCOL_ACCESS              Io;
  EFI_PCI_IO_PROTOCOL_CONFIG_ACCESS       Pci;
  EFI_PCI_IO_PROTOCOL_COPY_MEM            CopyMem;
  EFI_PCI_IO_PROTOCOL_MAP                 Map;
  EFI_PCI_IO_PROTOCOL_UNMAP               Unmap;
  EFI_PCI_IO_PROTOCOL_ALLOCATE_BUFFER     AllocateBuffer;
  EFI_PCI_IO_PROTOCOL_FREE_BUFFER         FreeBuffer;
  EFI_PCI_IO_PROTOCOL_FLUSH               Flush;
  EFI_PCI_IO_PROTOCOL_GET_LOCATION        GetLocation;
  EFI_PCI_IO_PROTOCOL_ATTRIBUTES          Attributes;
  EFI_PCI_IO_PROTOCOL_GET_BAR_ATTRIBUTES  GetBarAttributes;
  EFI_PCI_IO_PROTOCOL_SET_BAR_ATTRIBUTES  SetBarAttributes;

  ///
  /// The size, in bytes, of the ROM image.
  ///  UINT64                                  RomSize;

  ///
  /// A pointer to the in memory copy of the ROM image. The PCI Bus Driver is responsible
  /// for allocating memory for the ROM image, and copying the contents of the ROM to memory.
  /// The contents of this buffer are either from the PCI option ROM that can be accessed
  /// through the ROM BAR of the PCI controller, or it is from a platform-specific location.
  /// The Attributes() function can be used to determine from which of these two sources
  /// the RomImage buffer was initialized.
  ///  VOID                                    *RomImage;
};



今天我们只用到 EFI_PCI_IO_PROTOCOL_ACCESS              Io; 和 EFI_PCI_IO_PROTOCOL_CONFIG_ACCESS       Pci; 这两个子功能, EFI_PCI_IO_PROTOCOL_CONFIG_ACCESS 用于读写配置空间, EFI_PCI_IO_PROTOCOL_ACCESS用于读写Io空间。EFI_PCI_IO_PROTOCOL_CONFIG_ACCESS :

 /**
  Enable a PCI driver to access PCI controller registers in PCI configuration space.

  @param  This                  A pointer to the EFI_PCI_IO_PROTOCOL instance.
  @param  Width                 Signifies the width of the memory operations.
  @param  Offset                The offset within the PCI configuration space for the PCI controller.
  @param  Count                 The number of PCI configuration operations to perform.
  @param  Buffer                For read operations, the destination buffer to store the results. For write
                                operations, the source buffer to write data from.

  @retval EFI_SUCCESS           The data was read from or written to the PCI controller.
  @retval EFI_UNSUPPORTED       The address range specified by Offset, Width, and Count is not
                                valid for the PCI configuration header of the PCI controller.
  @retval EFI_OUT_OF_RESOURCES  The request could not be completed due to a lack of resources.
  @retval EFI_INVALID_PARAMETER Buffer is NULL or Width is invalid.

**/
typedef
EFI_STATUS
(EFIAPI *EFI_PCI_IO_PROTOCOL_CONFIG)(
  IN EFI_PCI_IO_PROTOCOL              *This,
  IN     EFI_PCI_IO_PROTOCOL_WIDTH    Width,
  IN     UINT32                       Offset,
  IN     UINTN                        Count,
  IN OUT VOID                         *Buffer
  );

typedef struct {
  ///
  /// Read PCI controller registers in PCI configuration space.
  ///  EFI_PCI_IO_PROTOCOL_CONFIG  Read;
  ///
  /// Write PCI controller registers in PCI configuration space.
  ///  EFI_PCI_IO_PROTOCOL_CONFIG  Write;
} EFI_PCI_IO_PROTOCOL_CONFIG_ACCESS;


EFI_PCI_IO_PROTOCOL_ACCESS:
typedef enum {
  EfiPciIoWidthUint8      = 0,
  EfiPciIoWidthUint16,
  EfiPciIoWidthUint32,
  EfiPciIoWidthUint64,
  EfiPciIoWidthFifoUint8,
  EfiPciIoWidthFifoUint16,
  EfiPciIoWidthFifoUint32,
  EfiPciIoWidthFifoUint64,
  EfiPciIoWidthFillUint8,
  EfiPciIoWidthFillUint16,
  EfiPciIoWidthFillUint32,
  EfiPciIoWidthFillUint64,
  EfiPciIoWidthMaximum
} EFI_PCI_IO_PROTOCOL_WIDTH;

/**
  Enable a PCI driver to access PCI controller registers in the PCI memory or I/O space.

  @param  This                  A pointer to the EFI_PCI_IO_PROTOCOL instance.
  @param  Width                 Signifies the width of the memory or I/O operations.
  @param  BarIndex              The BAR index of the standard PCI Configuration header to use as the
                                base address for the memory or I/O operation to perform.
  @param  Offset                The offset within the selected BAR to start the memory or I/O operation.
  @param  Count                 The number of memory or I/O operations to perform.
  @param  Buffer                For read operations, the destination buffer to store the results. For write
                                operations, the source buffer to write data from.

  @retval EFI_SUCCESS           The data was read from or written to the PCI controller.
  @retval EFI_UNSUPPORTED       BarIndex not valid for this PCI controller.
  @retval EFI_UNSUPPORTED       The address range specified by Offset, Width, and Count is not
                                valid for the PCI BAR specified by BarIndex.
  @retval EFI_OUT_OF_RESOURCES  The request could not be completed due to a lack of resources.
  @retval EFI_INVALID_PARAMETER One or more parameters are invalid.

**/
typedef
EFI_STATUS
(EFIAPI *EFI_PCI_IO_PROTOCOL_IO_MEM)(
  IN EFI_PCI_IO_PROTOCOL              *This,
  IN     EFI_PCI_IO_PROTOCOL_WIDTH    Width,     // 每个元素的大小
  IN     UINT8                                      BarIndex,       // 0~5中的一个
  IN     UINT64                                   Offset,            // 偏移
  IN     UINTN                                     Count,           //  元素个数
  IN OUT VOID                                   *Buffer
  );

typedef struct {
  ///
  /// Read PCI controller registers in the PCI memory or I/O space.
  ///  EFI_PCI_IO_PROTOCOL_IO_MEM  Read;
  ///
  /// Write PCI controller registers in the PCI memory or I/O space.
  ///  EFI_PCI_IO_PROTOCOL_IO_MEM  Write;
} EFI_PCI_IO_PROTOCOL_ACCESS;

例如读第0个bar里偏移为4的寄存器, 

UINT8 Data;
Status = PciIo->Io.Read(PciIo,
                         EfiPciIoWidthUint8 ,  //读一个字节
                        0,    // Bar 0
                        4,   // 偏移4字节
                        1,   // 一个元素
                        &Data);



AC97
Intel芯片组南桥中一般有AC97控制器芯片,AC97控制器通过AC-LINK与AC97 Codec进行通信,我们的音频驱动其实是AC97控制器驱动。
如何操作AC97控制器可以参考Intel® I/O Controller Hub 6 (ICH6) High Definition Audio / AC ’97, 这儿只做简单介绍。
AC97 IO空间有两个部分:Native Audio Bus Master Control Registers and Native Mixer Registers。 
Bar0 表示 Native Mixer Registers(NAMBAR) , 访问以16-bits为一个单位(与AC-LINK单位数据大小一致)。 用于配置AC97参数,如音量,采样率等。
Bar1 表示Bus Master Control Registers 。 用于控制AC97
Native Mixer Registers 功能表

Primary Offset    NAMBAR Exposed Registers
(Codec ID =00)
00h                    Reset
02h                    Master Volume
04h                    Aux Out Volume
06h                    Mono Volume
08h                    Master Tone (R & L)
0Ah                    PC_BEEP Volume
0Ch                    Phone Volume
0Eh                    Mic Volume
10h                    Line In Volume
12h                    CD Volume
14h                    Video Volume
16h                    Aux In Volume
18h                    PCM Out Volume
1Ah                    Record Select
1Ch                    Record Gain
1Eh                    Record Gain Mic
20h                    General Purpose
22h                    3D Control
24h                    AC ’97 RESERVED
26h                    Powerdown Ctrl/Stat
28h                    Extended Audio
2Ah                    Extended Audio Ctrl/Stat
2Ch                    PCM Front DAC Rate
2Eh                    PCM Surround DAC Rate
30h                    PCM LFE DAC Rate
32h                    PCM LR ADC Rate
34h                    MIC ADC Rate
36h                    6Ch Vol: C, LFE
38h                    6Ch Vol: L, R Surround
3Ah                    S/PDIF Control
3C~56h                    Intel RESERVED
58h                    AC ’97 Reserved
5Ah                    Vendor Reserved
7Ch                    Vendor ID1
7Eh                    Vendor ID2



Bus Master Control Registers 功能表

Mnemonic
Name
Default
Access
00h
PI_BDBAR
PCM In Buffer Descriptor list Base Address
00000000h
R/W
04h
PI_CIV
PCM In Current Index Value
00h
RO
05h
PI_LVI
PCM In Last Valid Index
00h
R/W
06h
PI_SR
PCM In Status
0001h
R/WC, RO
08h
PI_PICB
PCM In Position in Current Buffer
0000h
RO
0Ah
PI_PIV
PCM In Prefetched Index Value
00h
RO
0Bh
PI_CR
PCM In Control
00h
R/W, R/W (special)
10h
PO_BDBAR
PCM Out Buffer Descriptor list Base Address
00000000h
R/W
14h
PO_CIV
PCM Out Current Index Value
00h
RO
15h
PO_LVI
PCM Out Last Valid Index
00h
R/W
16h
PO_SR
PCM Out Status
0001h
R/WC, RO
18h
PO_PICB
PCM In Position In Current Buffer
0000h
RO
1Ah
PO_PIV
PCM Out Prefetched Index Value
00h
RO
1Bh
PO_CR
PCM Out Control
00h
R/W, R/W (special)
20h
MC_BDBAR
Mic. In Buffer Descriptor List Base Address
00000000h
R/W
24h
MC_CIV
Mic. In Current Index Value
00h
RO
25h
MC_LVI
Mic. In Last Valid Index
00h
R/W
26h
MC_SR
Mic. In Status
0001h
R/WC, RO
28h
MC_PICB
Mic. In Position In Current Buffer
0000h
RO
2Ah
MC_PIV
Mic. In Prefetched Index Value
00h
RO
2Bh
MC_CR
Mic. In Control
00h
R/W, R/W (special)
2Ch
GLOB_CNT
Global Control
00000000h
R/W, R/W (special)
30h
GLOB_STA
Global Status
See register description
R/W, R/WC, RO
34h
CAS
Codec Access Semaphore
00h
R/W (special)
40h
MC2_BDBAR
Mic. 2 Buffer Descriptor List Base Address
00000000h
R/W
44h
MC2_CIV
Mic. 2 Current Index Value
00h
RO
45h
MC2_LVI
Mic. 2 Last Valid Index
00h
R/W
46h
MC2_SR
Mic. 2 Status
0001h
RO, R/WC
48h
MC2_PICB
Mic 2 Position In Current Buffer
0000h
RO
4Ah
MC2_PIV
Mic. 2 Prefetched Index Value
00h
RO
4Bh
MC2_CR
Mic. 2 Control
00h
R/W, R/W (special)
50h
PI2_BDBAR
PCM In 2 Buffer Descriptor List Base Address
00000000h
R/W
54h
PI2_CIV
PCM In 2 Current Index Value
00h
RO
55h
PI2_LVI
PCM In 2 Last Valid Index
00h
R/W
56h
PI2_SR
PCM In 2 Status
0001h
R/WC, RO
58h
PI2_PICB
PCM In 2 Position in Current Buffer
0000h
RO
5Ah
PI2_PIV
PCM In 2 Prefetched Index Value
00h
RO
5Bh
PI2_CR
PCM In 2 Control
00h
R/W, R/W (special)
60h
SPBAR
S/PDIF Buffer Descriptor List Base Address
00000000h
R/W
64h
SPCIV
S/PDIF Current Index Value
00h
RO
65h
SPLVI
S/PDIF Last Valid Index
00h
R/W
66h
SPSR
S/PDIF Status
0001h
R/WC, RO
68h
SPPICB
S/PDIF Position In Current Buffer
0000h
RO
6Ah
SPPIV
S/PDIF Prefetched Index Value
00h
RO
6Bh
SPCR
S/PDIF Control
00h
R/W, R/W (special)
80h
SDM
SData_IN Map
00h
R/W, RO

表中有5个通道

PI = PCM in channel
PO = PCM out channel
MC = Mic in channel
MC2 = Mic 2 channel
PI2 = PCM in 2 channel
SP = S/PDIF out channel.

每个通道有一个16-bit DMA引擎,用于传输音频数据(PCM格式)。 DMA引擎使用Buffer Descriptor List 数据结构获得数据缓冲区地址。Buffer Descriptor List 最多允许32项,Buffer Descriptor 声明如下:

typedef struct {
    UINT32 addr;
    UINT16 len;
    unsigned short reserved:14;
    unsigned short BUP:1;
    unsigned short IOC:1;
} BufferDescriptor;

addr的第0个bit必须是0。 List中最后一个BufferDescriptor 的BUP需为1。每个buffer最多有65536个采样。
启动DMA的过程如下(翻译自:Intel® I/O Controller Hub 6 (ICH6) High Definition Audio / AC ’97 Programmer’s Reference Manual (PRM) :30页):
1. 建立buffer descriptor list。
2. 将buffer descriptor list 的基地址写入Buffer Descriptor List Base Address register(对PCM Out而言是 MBBAR + 10h (POBAR))
3. 填充buffer descriptor list 中的buffer descriptor,并设置好数据缓存。
4. 设置 Last Valid Index(LVI)寄存器 (PCM OUT: MBBAR + 15h (POLVI))。从头开始播放则设为0。
5. 设置Control register中的run bit,启动DMA传输。
这时就可以听到声音了。

AC97驱动

现在我们开始设计AC97驱动。时候还记得我们讲过驱动分两个部分,硬件相关部分和框架部分。首先来设计硬件相关部分,也就是驱动硬件并提供给用户使用的协议。

EFI_AUDIO_PROTOCOL

我们把要提供的服务命名为EFI_AUDIO_PROTOCOL,在EFI_AUDIO_PROTOCOL, 我们提供播放音频的服务,首先我们要提供硬件初始化服务,还要提供PCM音频播放服务,音量调节服务,还要提供一个Event用来通知用户音频播放结束。
在audio.h中定义EFI_AUDIO_PROTOCOL相关数据结构


struct _EFI_AUDIO_PROTOCOL{
 UINT64          Revision;
 EFI_AC97_RESET  Reset;
 EFI_AC97_PLAY   Play;
 EFI_AC97_VOLUME Volume;
 EFI_EVENT       WaitForEndEvent;
}

设计EFI_AUDIO_PROTOCOL的GUID

#define EFI_AUDIO_PROTOCOL_GUID 

    0xce345171, 0xabcd, 0x11d2, {0x8e, 0x4f, 0x0, 0xa0, 0xc9, 0x69, 0x72, 0x3b } 
}



在accdriver.c中还要定义用于标示音频播放上下文的数据结构,一般命名为X_PRIVATE_DATA。在上下文中我们要包含EFI_AUDIO_PROTOCOL实例,设备的EFI_PCI_IO_PROTOCOL实例,BufferDescriptor。

typedef struct {
    UINTN                 Signature;
    EFI_AUDIO_PROTOCOL  Audio;
    EFI_PCI_IO_PROTOCOL   *PciIo;
    BufferDescriptor Bdes[32];
} AUDIO_PRIVATE_DATA;

定义EFI_AUDIO_PROTOCOL Template,用于在Start函数中初始化EFI_AUDIO_PROTOCOL实例

//
// Template for Audio private data structure.// The pointer to Audio protocol interface is assigned dynamically.//AUDIO_PRIVATE_DATA        gDiskIoPrivateDataTemplate = {
  AUDIO_PRIVATE_DATA_SIGNATURE,
  {
    EFI_AUDIO_PROTOCOL_REVISION,
    AC97Reset,
    AC97Play,
    AC97Volume,
    0
  },
  NULL,
  {0}
};

下面要实现 EFI_AUDIO_PROTOCOL 中定义的三个服务,每个成员函数(服务)的第一个参数是This指针,在函数里首先要根据This指针获得上下文Private,然后根据上下文执行相应操作。
首先是Reset,主要是设置Mixer Register里德Powerdown Ctrl/Stat和Reset寄存器, 可以参考WinDDK里AC97驱动示例。


/**
  @param  This       Indicates a pointer to the calling context.

  @retval EFI_SUCCESS          .
  @retval EFI_DEVICE_ERROR         .

**/
EFI_STATUS
EFIAPI AC97Reset(
        IN  EFI_AUDIO_PROTOCOL  *This
        )
{
    NTSTATUS InitAC97 (void);
    EFI_STATUS            Status;
    AUDIO_PRIVATE_DATA* Private = AUDIO_PRIVATE_DATA_FROM_THIS(This);
    gAudioPciIo = Private->PciIo;

    Status = InitAC97 ();
    return Status;
}

然后是Play函数, 首先设置Buffer Descriptor List,然后设置POBAR和PO_LVI  ,     最后启动DMA。

/**
  @param  This       Indicates a pointer to the calling context.
  @param  PcmData    Pointer to PCM Data
  @param  Format     PCM Data Format
  @param  Size       How many Samples in PCM Data

  @retval EFI_SUCCESS          .
  @retval EFI_DEVICE_ERROR         .

**/
EFI_STATUS
EFIAPI AC97Play(
        IN  EFI_AUDIO_PROTOCOL  *This,
    IN  UINT8* PcmData,
    IN  UINT32 Format,
    IN  UINTN Size
        )
{
    EFI_STATUS            Status;
    UINTN i=0, LenLeft = Size;
    AUDIO_PRIVATE_DATA* Private = AUDIO_PRIVATE_DATA_FROM_THIS(This);
    gAudioPciIo = Private->PciIo;

    for( i=0; i< 32 && LenLeft > 0; i++, LenLeft-=65536){
        Private->Bdes[0].addr = (u32)(PcmData + 65536 * 4 * i);
        Private->Bdes[0].len = (u16)(LenLeft <= 65536 ? LenLeft:LenLeft-65536);
    }
    Private->Bdes[i-1].BUP = 1;
    Private->Bdes[i-1].IOC = 0;

    WriteBMControlRegister32(PO_BDBAR       ,  (u32)Private->Bdes);
    WriteBMControlRegister(PO_LVI         ,  0);

    WriteBMControlRegisterMask(PO_CR           , 1,1);    //启动DMA

    (void) Status;
    return EFI_SUCCESS;
}

设置音量的函数

/**
  @param  This       Indicates a pointer to the calling context.
  @param  Increase   How much should the volume change,
                     +Number increase; -Number Decrease.
  @param  NewVolume   if *NewVolume >=0 , It will set the volume as *NewVolume;
                      if *NewVolume <0, the Volume will be changed by Increase,
              and *Newvolume returns the current Volume.

  @retval EFI_SUCCESS          .
  @retval EFI_DEVICE_ERROR         .

**/
EFI_STATUS
EFIAPI AC97Volume(
        IN  EFI_AUDIO_PROTOCOL  *This,
    IN  INT32 Increase,
    IN OUT INT32 * NewVolume
    )
{
    AUDIO_PRIVATE_DATA* Private = AUDIO_PRIVATE_DATA_FROM_THIS(This);
    gAudioPciIo = Private->PciIo;

    if(*NewVolume < 0){
        WORD data= (WORD) (long ) NewVolume;
        WriteCodecRegister (AC97REG_PCM_OUT_VOLUME, data, 0xFFFF);
    }else{
        WORD data= 0;
        ReadCodecRegister(AC97REG_PCM_OUT_VOLUME, &data);
        data += (INT16) Increase;
        WriteCodecRegister (AC97REG_PCM_OUT_VOLUME, data, 0xFFFF);
        *NewVolume = (INT32)data;
    }
    return EFI_SUCCESS;
}



驱动的框架部分
驱动的框架部分主要是实现EFI_DRIVER_BINDING_PROTOCOL及Image的初始化函数

//
// Driver binding protocol implementation for AC97 driver.//EFI_DRIVER_BINDING_PROTOCOL gAudioDriverBinding = {
  AC97DriverBindingSupported,
  AC97DriverBindingStart,
  AC97DriverBindingStop,
  0xa,
  NULL,
  NULL
};

(1)Supported(AC97DriverBindingSupported)函数用来检测设备是否AC97驱动器。分两步:1。 判断Controller时候有EFI_PCI_IO_PROTOCOL  , 没有则返回错误。2。  有EFI_PCI_IO_PROTOCOL 则读取PCI配置空间,判断设备时候AC97驱动器。         

/**
  Test to see if this driver supports ControllerHandle.

  @param  This                Protocol instance pointer.
  @param  ControllerHandle    Handle of device to test
  @param  RemainingDevicePath Optional parameter use to pick a specific child
                              device to start.

  @retval EFI_SUCCESS         This driver supports this device
  @retval EFI_ALREADY_STARTED This driver is already running on this device
  @retval other               This driver does not support this device

**/
EFI_STATUS
EFIAPI
AC97DriverBindingSupported (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   ControllerHandle,
  IN EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath OPTIONAL
  )
{
  EFI_STATUS            Status;
  PCI_TYPE00                        PciData;
  EFI_PCI_IO_PROTOCOL       *PciIo;
  Status = gBS->OpenProtocol(
          ControllerHandle,
          &gEfiPciIoProtocolGuid,
          (VOID**)&PciIo,
          This->DriverBindingHandle,
          ControllerHandle,
          EFI_OPEN_PROTOCOL_BY_DRIVER
            );
   if (EFI_ERROR (Status)) {
       return Status;
   }

   Status = PciIo->Pci.Read (
           PciIo,
           EfiPciIoWidthUint32,
           0,
           sizeof (PciData) / sizeof (UINT32),
           &PciData
           );

   gBS->CloseProtocol (
        ControllerHandle,
        &gEfiPciIoProtocolGuid,
        This->DriverBindingHandle,
        ControllerHandle
        );

   if (EFI_ERROR (Status)) {
       return Status;
   }
   if (!(PciData.Hdr.ClassCode[2] == PCI_CLASS_MEDIA && PciData.Hdr.ClassCode[1] == PCI_CLASS_MEDIA_AUDIO && PciData.Hdr.ClassCode[0] == 0x00) ) {
       return EFI_UNSUPPORTED;
   }
  return EFI_SUCCESS;
}



(2) Start(AC97DriverBindingStart) 启动设备,安装EFI_AUDIO_PROTOCOL。 

/**
  Start this driver on ControllerHandle by opening a PCI IO protocol and
  installing a Audio IO protocol on ControllerHandle.

  @param  This                 Protocol instance pointer.
  @param  ControllerHandle     Handle of device to bind driver to
  @param  RemainingDevicePath  Optional parameter use to pick a specific child
                               device to start.

  @retval EFI_SUCCESS          This driver is added to ControllerHandle
  @retval EFI_ALREADY_STARTED  This driver is already running on ControllerHandle
  @retval other                This driver does not support this device

**/
EFI_STATUS
EFIAPI
AC97DriverBindingStart (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   ControllerHandle,
  IN EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath OPTIONAL
  )
{
  EFI_STATUS            Status;
  EFI_PCI_IO_PROTOCOL   *PciIo;
  AUDIO_PRIVATE_DATA    *Private;

  Status = gBS->OpenProtocol(
          ControllerHandle,
          &gEfiPciIoProtocolGuid,
          (VOID**)&PciIo,
          This->DriverBindingHandle,
          ControllerHandle,
          EFI_OPEN_PROTOCOL_BY_DRIVER
            );
   if (EFI_ERROR (Status)) {
       return Status;
   }
   //
   // Allocate a buffer to store the ATA_ATAPI_PASS_THRU_INSTANCE data structure
   //   Private = AllocateCopyPool (sizeof (AUDIO_PRIVATE_DATA),        &gDiskIoPrivateDataTemplate );
   if (Private == NULL) {
       goto ErrorExit;
   }

   Private->PciIo = PciIo;
   Status = gBS->CreateEvent(EVT_NOTIFY_WAIT, TPL_NOTIFY, (EFI_EVENT_NOTIFY)PlayEndEventNoify, (VOID*)Private, &Private->Audio.WaitForEndEvent);

   Status = gBS->InstallProtocolInterface (
                  &ControllerHandle,
                  &gEfiAudioProtocolGUID,
                  EFI_NATIVE_INTERFACE,
                  &Private->Audio
                  );

ErrorExit:
   if (EFI_ERROR (Status)) {

    if (Private != NULL) {
      FreePool (Private);
    }

    gBS->CloseProtocol (
          ControllerHandle,
          &gEfiPciIoProtocolGuid,
          This->DriverBindingHandle,
          ControllerHandle
          );
  }else{
   // Init Ac97
   AC97Reset(&Private->Audio);
  }
   return Status;
}

(3)Stop(AC97DriverBindingStop)函数


/**
  Stop this driver on ControllerHandle by removing Audio IO protocol and closing
  the PCI IO protocol on ControllerHandle.

  @param  This              Protocol instance pointer.
  @param  ControllerHandle  Handle of device to stop driver on
  @param  NumberOfChildren  Number of Handles in ChildHandleBuffer. If number of
                            children is zero stop the entire bus driver.
  @param  ChildHandleBuffer List of Child Handles to Stop.

  @retval EFI_SUCCESS       This driver is removed ControllerHandle
  @retval other             This driver was not removed from this device

**/
EFI_STATUS
EFIAPI
AC97DriverBindingStop (
  IN  EFI_DRIVER_BINDING_PROTOCOL    *This,
  IN  EFI_HANDLE                     ControllerHandle,
  IN  UINTN                          NumberOfChildren,
  IN  EFI_HANDLE                     *ChildHandleBuffer
  )
{
  EFI_STATUS            Status;
  AUDIO_PRIVATE_DATA    *Private;
  EFI_AUDIO_PROTOCOL    *Audio;

  //
  // Get our context back.
  //  Status = gBS->OpenProtocol (
                  ControllerHandle,
                  &gEfiAudioProtocolGUID,
                  (VOID **) &Audio,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_GET_PROTOCOL
                  );
  if (EFI_ERROR (Status)) {
    return EFI_UNSUPPORTED;
  }

  Private = AUDIO_PRIVATE_DATA_FROM_THIS(This);

  Status = gBS->UninstallProtocolInterface (
                  ControllerHandle,
                  &gEfiAudioProtocolGUID,
                  &Private->Audio
                  );
  if (!EFI_ERROR (Status)) {
    Status = gBS->CloseProtocol (
                    ControllerHandle,
                    &gEfiPciIoProtocolGuid,
                    This->DriverBindingHandle,
                    ControllerHandle
                    );
  }

  if (!EFI_ERROR (Status)) {
    FreePool (Private);
  }

  return Status;
}



最后要在Image的入口函数安装EFI_DRIVER_BINDING_PROTOCOL


EFI_STATUS
EFIAPI
InitializeACC(
        IN EFI_HANDLE        ImageHandle,
        IN EFI_SYSTEM_TABLE  *SystemTable
        )
{
    EFI_STATUS Status;
    //
    // Install driver model protocol(s).
    //    Status = EfiLibInstallDriverBindingComponentName2 (
            ImageHandle,
            SystemTable,
            &gAudioDriverBinding,
            ImageHandle,
            &gAudioComponentName,
            &gAudioComponentName2
            );
    //ASSERT_EFI_ERROR (Status);
    return Status;
}

自此,驱动模型就介绍完了。 

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

UEFI支持FAT格式的文件系统。在UEFI中我们有两种读写文件的方式,一是利用EFI_FILE_PROTOCOL, 二是利用EFI_SHELL_PROTOCOL。EFI_SHELL_PROTOCOL的使用相对简单,是对EFI_FILE_PROTOCOL做了封装和扩充,提供基本的文件访问函数之外,还提供了相对目录的操作方式以及stdin,stdout,stderr。

EFI_FILE_PROTOCOL

首先找出FAT分区的controller,然后打开EFI_SIMPLE_FILE_SYSTEM_PROTOCOL, 如果系统中只有一个FAT分区,用gBS->LocateProtocol(…)就可以了。EFI_SIMPLE_FILE_SYSTEM_PROTOCOL 只提供了一个方法OpenVolume,用来打开设备,获得 EFI_FILE_PROTOCOL实例。

typedef struct _EFI_SIMPLE_FILE_SYSTEM_PROTOCOL {
UINT64 Revision;
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_OPEN_VOLUME OpenVolume;
} EFI_SIMPLE_FILE_SYSTEM_PROTOCOL;

typedef
EFI_STATUS
(EFIAPI *EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_OPEN_VOLUME) (
IN EFI_SIMPLE_FILE_SYSTEM PROTOCOL *This,
OUT EFI_FILE_PROTOCOL **Root
);

    EFI_STATUS                      Status;
    EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *SimpleFileSystem;
    EFI_FILE_PROTOCOL               *FileProtocol;

        Status = gBS->LocateProtocol(
            &gEfiSimpleFileSystemProtocolGuid,
                NULL,
            (VOID**)&SimpleFileSystem
        );
    if (EFI_ERROR(Status)) {
        Print(L”Open FileSystemProtocol errn”);
        return (EFI_NOT_FOUND);
    }else {
        Print(L”Open FileSystemProtocol OKn”);
    }
    Status = SimpleFileSystem->OpenVolume(SimpleFileSystem, &FileProtocol);

得到该分区上EFI_FILE_PROTOCOL实例的指针后,就可以操作该分区上的文件了。

EFI_FILE_PROTOCOL结构如下:
typedef struct _EFI_FILE_PROTOCOL {
UINT64 Revision;
EFI_FILE_OPEN Open;
EFI_FILE_CLOSE Close;
EFI_FILE_DELETE Delete;
EFI_FILE_READ Read;
EFI_FILE_WRITE Write;
EFI_FILE_GET_POSITION GetPosition;
EFI_FILE_SET_POSITION SetPosition;
EFI_FILE_GET_INFO GetInfo;
EFI_FILE_SET_INFO SetInfo;
EFI_FILE_FLUSH Flush;
} EFI_FILE_PROTOCOL;

读写文件与我们常用的标准C读写文件大同小异。例如写文件

            UINTN                           LbufSize;
            CHAR16                          *Lbuf= (CHAR16*)L”This is test filen”;

            EFI_FILE_PROTOCOL *FileHandle = 0;

            Status = FileProtocol->Open(FileProtocol, &FileHandle, ( CHAR16*)L”testfileprotocol.txt”,  EFI_FILE_MODE_CREATE | EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE    , 0);
            CheckStatus(L” EFI_FILE_PROTOCOL Create file “);
            if( FileHandle && !EFI_ERROR(Status)) {
                LbufSize = StrLen(Lbuf) * 2;
                Status = FileProtocol->Write (FileHandle, &LbufSize, Lbuf);
                CheckStatus(L”EFI_FILE_PROTOCOL Write file “);
                Status = FileProtocol->Close (FileHandle);
                CheckStatus(L”EFI_FILE_PROTOCOL Close file “);
            }

下面是Open/Read/Write/Close的函数原型,以供参考

typedef
EFI_STATUS
(EFIAPI *EFI_FILE_OPEN) (
IN EFI_FILE_PROTOCOL *This,
OUT EFI_FILE_PROTOCOL **NewHandle,
IN CHAR16 *FileName,
IN UINT64 OpenMode,
IN UINT64 Attributes
);
typedef
EFI_STATUS
(EFIAPI *EFI_FILE_CLOSE) (
IN EFI_FILE_PROTOCOL *This
);

typedef
EFI_STATUS
(EFIAPI *EFI_FILE_READ) (
IN EFI_FILE_PROTOCOL *This,
IN OUT UINTN *BufferSize,
OUT VOID *Buffer
);
typedef
EFI_STATUS
(EFIAPI *EFI_FILE_WRITE) (
IN EFI_FILE_PROTOCOL *This,
IN OUT UINTN *BufferSize,
IN VOID *Buffer
);

值得注意的是:
1. Open函数的第一个参数是EFI_FILE_PROTOCOL实例的指针。其它函数的第一个参数是通过Open得到的文件Handle,其类型被强制转换成了EFI_FILE_PROTOCOL*.
2. Open函数的第三个参数是绝对路径,例如本例中的testfileprotocol.txt会产生在该分区的根目录。

EFI_SHELL_PROTOCOL读写文件


typedef struct _EFI_SHELL_PROTOCOL {
  EFI_SHELL_EXECUTE                         Execute;
  EFI_SHELL_GET_ENV                         GetEnv;
  EFI_SHELL_SET_ENV                         SetEnv;
  EFI_SHELL_GET_ALIAS                       GetAlias;
  EFI_SHELL_SET_ALIAS                       SetAlias;
  EFI_SHELL_GET_HELP_TEXT                   GetHelpText;
  EFI_SHELL_GET_DEVICE_PATH_FROM_MAP        GetDevicePathFromMap;
  EFI_SHELL_GET_MAP_FROM_DEVICE_PATH        GetMapFromDevicePath;
  EFI_SHELL_GET_DEVICE_PATH_FROM_FILE_PATH  GetDevicePathFromFilePath;
  EFI_SHELL_GET_FILE_PATH_FROM_DEVICE_PATH  GetFilePathFromDevicePath;
  EFI_SHELL_SET_MAP                         SetMap;
  EFI_SHELL_GET_CUR_DIR                     GetCurDir;
  EFI_SHELL_SET_CUR_DIR                     SetCurDir;
  EFI_SHELL_OPEN_FILE_LIST                  OpenFileList;
  EFI_SHELL_FREE_FILE_LIST                  FreeFileList;
  EFI_SHELL_REMOVE_DUP_IN_FILE_LIST         RemoveDupInFileList;
  EFI_SHELL_BATCH_IS_ACTIVE                 BatchIsActive;
  EFI_SHELL_IS_ROOT_SHELL                   IsRootShell;
  EFI_SHELL_ENABLE_PAGE_BREAK               EnablePageBreak;
  EFI_SHELL_DISABLE_PAGE_BREAK              DisablePageBreak;
  EFI_SHELL_GET_PAGE_BREAK                  GetPageBreak;
  EFI_SHELL_GET_DEVICE_NAME                 GetDeviceName;
  EFI_SHELL_GET_FILE_INFO                   GetFileInfo;
  EFI_SHELL_SET_FILE_INFO                   SetFileInfo;
  EFI_SHELL_OPEN_FILE_BY_NAME               OpenFileByName;
  EFI_SHELL_CLOSE_FILE                      CloseFile;
  EFI_SHELL_CREATE_FILE                     CreateFile;
  EFI_SHELL_READ_FILE                       ReadFile;
  EFI_SHELL_WRITE_FILE                      WriteFile;
  EFI_SHELL_DELETE_FILE                     DeleteFile;
  EFI_SHELL_DELETE_FILE_BY_NAME             DeleteFileByName;
  EFI_SHELL_GET_FILE_POSITION               GetFilePosition;
  EFI_SHELL_SET_FILE_POSITION               SetFilePosition;
  EFI_SHELL_FLUSH_FILE                      FlushFile;
  EFI_SHELL_FIND_FILES                      FindFiles;
  EFI_SHELL_FIND_FILES_IN_DIR               FindFilesInDir;
  EFI_SHELL_GET_FILE_SIZE                   GetFileSize;
  EFI_SHELL_OPEN_ROOT                       OpenRoot;
  EFI_SHELL_OPEN_ROOT_BY_HANDLE             OpenRootByHandle;
  EFI_EVENT                                 ExecutionBreak;
  UINT32                                    MajorVersion;
  UINT32                                    MinorVersion;
} EFI_SHELL_PROTOCOL;
EFI_SHELL_PROTOCOL 中文件相关函数比EFI_FILE_PROTOCOL多了一些,提供了目录相关操作,丰富了打开与关闭操作。 产生文件使用CreateFile。最重要的是文件操作使用相对路径。当前路径可以通过SetCurDir 和GetCurDir操作。

EFI_STATUS
testFile(IN EFI_HANDLE ImageHandle)
{
    EFI_STATUS                      Status;
    SHELL_FILE_HANDLE        FileHandle;
    UINTN                           WbufSize;
    UINTN                           RbufSize = 256;
    CHAR16                          *Wbuf= (CHAR16*)L”This is test filen”;
    CHAR16                          *Rbuf= new CHAR16[256] ;

        ////////////////////////////        Create&Write           /////////////////////////////////////////////    Status = gEfiShellProtocol->CreateFile((CONST CHAR16*)L”testfile.txt”, EFI_FILE_MODE_WRITE, &FileHandle);
    CheckStatus(L”Create file “);

    LbufSize = StrLen(Lbuf) * 2;   
    Status = gEfiShellProtocol->WriteFile(FileHandle, &WbufSize, Wbuf);
    CheckStatus(L”Write file “);

    Status = gEfiShellProtocol->CloseFile(FileHandle);
    CheckStatus(L”Close file “);

    ////////////////////////////        Read           /////////////////////////////////////////////    for(UINTN i =0; i< 256;i++) Rbuf[i] = 0;

    Status = gEfiShellProtocol->OpenFileByName((CONST CHAR16*)L”testfile.txt”, &FileHandle, EFI_FILE_MODE_READ);
    CheckStatus(L”Open file “);
    RbufSize = 256;
    Status = gEfiShellProtocol->ReadFile(FileHandle, &RbufSize ,Rbuf  );

    AsciiPrint(“Reading %d: %sn”,RbufSize, (CHAR8*)Rbuf);

    DebugStep (L”read”);
    Status = gEfiShellProtocol->CloseFile(FileHandle);
    CheckStatus(L”Close file “);
    return EFI_SUCCESS;
}

注意 gEfiShellProtocol是EDK2中全局变量。

       testfile.txt会产生在当前目录下。

未經允許不得轉載:GoMCU » [轉] UEFI实战