UEFI编程入门-环境准备 环境准备 准备 需要安装Visual Studio 2019、Python 2.7+、IASL编译器、NASM编译器,并将后两者加入PATH。
其中IASL编译器下载地址https://www.intel.com/content/www/us/en/developer/topic-technology/open/acpica/download.html 。NASM编译器下载地址https://www.nasm.us/pub/nasm/releasebuilds/2.16.03/win64/ 。
下载EDK2开发包:
1 2 git clone https://github.com/tianocore/edk2.git git clone https://github.com/tianocore/edk2-libc.git
EDK2中通过Submodule方式提供了些必要的库文件和编译所需源文件,这里对他们进行初始化和更新,在edk2目录下:
1 git submodule update --init
再在edk2目录下编译BaseTools,这里要打开Visual Studio的x86 Native Tools Command Prompt。编译好的BaseTools在BaseTools目录中。
接着为了方便后续添加新开发包,这里编写一个设置开发工具路径的批处理。在edk2和edk2-libc同目录下,新建mybuild.bat,添加:
1 2 3 4 set WORKSPACE=%CD%set EDK_TOOLS_PATH=%CD%\edk2\BaseToolsset CONF_PATH=%CD%\edk2\confset PACKAGES_PATH=%CD%\edk2;%CD%\edk2-libc
UEFI编译依赖edk2/Conf目录下的target.txt和tools_def.txt。分别指定编译默认参数和编译工具链。例如target.txt的设置:
1 2 3 4 5 6 ACTIVE_PLATFORM = EmulatorPkg/EmulatorPkg.dsc #目前正在编译的包 TARGET = DEBUG #编译目标类型 可以是DEBUG、RELEASE、NOOPT TARGET_ARCH = IA32 #程序运行目标架构 可以是IA32、IPF、X64、EBC、ARM、AARCH64 TOOL_CHAIN_CONF = Conf/tools_def.txt #编译链工具配置文件位置 TOOL_CHAIN_TAG = VS2019 #编译工具链 BUILD_RULE_CONF = Conf/build_rule.txt #编译规则文件
在tools_def.txt中有大量可用编译器,这个就不需要管了。
编译 先编译UEFI模拟器EmulatorPkg,在x86 Native Tools Command Prompt中:
1 2 3 mybuild.bat edk2\edksetup.bat build -p edk2\EmulatorPkg\EmulatorPkg.dsc -t VS2022 -a IA32
再编译UEFI程序,同样在x86 Native Tools Command Prompt中:
1 2 3 4 5 6 7 8 9 mybuild.bat edk2\edksetup.bat build -p edk2-libc\AppPkg\AppPkg.dsc -t VS2022 -a IA32 mybuild.bat edk2\edksetup.bat build -p edk2\MdeModulePkg\MdeModulePkg.dsc -m edk2\MdeModulePkg\Application\HelloWorld\HelloWorld.inf -t VS2022 -a IA32
编译出来的目标程序都在Build目录下。
模拟 运行WinHost:
1 2 cd Build\EmulatorIA32\DEBUG_VS2022\IA32WinHost.exe
成功启动后进入UEFI Shell,显示挂载了一些设备,其中FS0为主机Build\EmulatorIA32\DEBUG_VS2022\IA32目录。UEFI Shell命令与DOS或Bash差不多,如ls
或dir
、pci
等,用help -b
查看帮助。这里运行helloworld:
调试 Visual Studio 2022联动 这里对HelloWorld.efi进行调试。在edk2\MdeModulePkg\Application\HelloWorld目录下用Visual Studio 2022新建一个生成文件项目(Makefile Project),工程名随便起,这里叫x43dbg_vs,其中生成命令行、清除命令行和重新生成命令行先随便填,接下来要改,其他不用管。之后打开x32dbg_vs.vcxproj并修改对应DEBUG选项的NMakeBuildCommandLine标签内容:
1 2 3 4 5 cd /D D:\tests\UEFItestset WORKSPACE=D:\tests\UEFItestcall mybuild.bat call edk2\edksetup.bat call build.bat -p EmulatorPkg\EmulatorPkg.dsc -a IA32 -m MdeModulePkg\Application\HelloWorld\HelloWorld.inf -b DEBUG
修改NMakeCleanCommandLine标签内容:
1 2 3 4 5 cd /D D:\tests\UEFItestset WORKSPACE=D:\tests\UEFItestcall mybuild.bat call edk2\edksetup.bat call build.bat -p EmulatorPkg\EmulatorPkg.dsc -a IA32 -m MdeModulePkg\Application\HelloWorld\HelloWorld.inf -b DEBUG clean
修改NMakeReBuildCommandLine标签内容:
1 2 3 4 5 6 cd /D D:\tests\UEFItestset WORKSPACE=D:\tests\UEFItestcall mybuild.bat call edk2\edksetup.bat call build.bat -p EmulatorPkg\EmulatorPkg.dsc -a IA32 -m MdeModulePkg\Application\HelloWorld\HelloWorld.inf -b DEBUG clean call build.bat -p EmulatorPkg\EmulatorPkg.dsc -a IA32 -m MdeModulePkg\Application\HelloWorld\HelloWorld.inf -b DEBUG
再手动把edk2\MdeModulePkg\Application\HelloWorld\HelloWorld.c导入源文件下,之后进行Build,发现下方输出窗口内容同上述手动编译过程。然后在项目属性中设置调试->工作目录为D:\tests\UEFItest\Build\EmulatorIA32\DEBUG_VS2022\IA32\,命令改为WinHost.exe。之后对源码打断点,并用F5启动调试。随即启动WinHost.exe,在其中运行HelloWorld.efi,发现在Visual Studio中命中断点。
QEMU+WinDBG联动 先下载QEMU和Intel UDK Debugger Tool,前者在https://qemu.weilnetz.de/w64/ ,后者在https://www.intel.com/content/www/us/en/developer/articles/tool/unified-extensible-firmware-interface.html 。Intel UDK Debugger Tool安装时选择Pipe类型,Port填qemu_pipe_dbg。WinDBG和Intel UDK Debugger Tool安装目录上不要有汉字。
接下来为QEMU创建VHD虚拟硬盘。打开Windows自带磁盘管理工具,选中某个磁盘创建VHD,200MB大小即可,命名为lbdbg.vhd,并设为动态增长模式。然后对刚生成的虚拟硬盘文件装载,格式化为FAT32分区,再弹出。
接着用OvmfPkg制作一个支持源码级调试的BIOS镜像,例如32位:
1 build -a IA32 -p edk2\OvmfPkg\OvmfPkgIa32.dsc -b NOOPT -D SOURCE_DEBUG_ENABLE
编译出的文件位于Build\OvmfIa32\NOOPT_VS2022\FV目录下的OVMF.fd,并将HelloWorld.efi复制到lbdbg.vhd分区中,弹出。找个地方新建dbgOvmfIa32文件夹,将lbdbg.vhd和OVMF.fd复制到这里。打开开始菜单的Start Windbg With Intel UDK Debugger Tool,如果弹出不支持该内核调试方法,则自行启动管道调试。在dbgOvmfIa32目录打开命令行,运行:
1 qemu-system-i386 -L . -bios OVMF.fd -hdd lbdbg.vhd -serial pipe:qemu_pipe_dbg
运行后用下面命令下断点,并用g
继续:
制作启动盘 这里只讲针对UEFI BIOS制作的启动盘。启动文件通过编译ShellPkg得到:
1 2 3 mybuild.bat edk2\edksetup.bat build -a IA32 -a X64 -p ShellPkg\ShellPkg.dsc -t VS2022 -b RELEASE
在Build\Shell\RELEASE_VS2022下的IA32和X64目录下各有一个Shell.efi,所处目录类似于Build\Shell\RELEASE_VS2022\IA32\ShellPkg\Application\Shell\EA4BB293-2D7F-4456-A681-1F22F42CD0BC\OUTPUT,分别改名为bootx32.efi和bootx64.efi。之后将U盘格式化为FAT32格式,在U盘根目录下建立efi\boot目录,将bootx32.efi和bootx64.efi复制到该目录下。重启计算机,开机从U盘启动即可进入UEFI Shell。
应用构建 配置文件 代码中一定不要有中文,注释也不行,否则编译出问题。
HelloWorld的INF配置文件如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 [Defines] INF_VERSION = 0 x00010005 BASE_NAME = HelloWorld MODULE_UNI_FILE = HelloWorld.uni FILE_GUID = 6987936 E-ED34-44 db-AE97-1 FA5E4ED2116 MODULE_TYPE = UEFI_APPLICATION VERSION_STRING = 1.0 ENTRY_POINT = UefiMain UEFI_HII_RESOURCE_SECTION = TRUE [Sources] HelloWorld.c HelloWorldStr.uni [Packages] MdePkg/MdePkg.dec MdeModulePkg/MdeModulePkg.dec [LibraryClasses] UefiApplicationEntryPoint UefiLib PcdLib [FeaturePcd] gEfiMdeModulePkgTokenSpaceGuid.PcdHelloWorldPrintEnable [Pcd] gEfiMdeModulePkgTokenSpaceGuid.PcdHelloWorldPrintString gEfiMdeModulePkgTokenSpaceGuid.PcdHelloWorldPrintTimes [UserExtensions.TianoCore."ExtraFiles"] HelloWorldExtra.uni
每次新建工程时,在edk2-lib\AppPkg\Application\目录下新建文件夹,文件夹中需要有一个.c源代码和一个.inf文件,然后打开对应包下的DSC文件,在Components节中加入:
1 2 [Components] AppPkg/Applications/程序名/源代码名.inf
入口函数 UEFI应用的入口函数有三种,功能不同。对于UefiMain
型的:
1 2 3 4 5 6 7 8 9 10 11 #include <Uefi.h> #include <Library/UefiLib.h> EFI_STATUS EFIAPI UefiMain (IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE* SystemTable) { EFI_TIME curTime; Print (L"Hello,this is Entry of UefiMain!\n" ); SystemTable->BootServices->Stall (2000 ); SystemTable->RuntimeServices->GetTime (&curTime, NULL ); Print (L"Current Time: %d-%d-%d %02d:%02d:%02d\n" , curTime.Year, curTime.Month, curTime.Day, curTime.Hour, curTime.Minute, curTime.Second); SystemTable->ConOut->OutputString (SystemTable->ConOut, L"Test SystemTable...\n\r" ); return EFI_SUCCESS; };
ImageHandle指向模块自身加载到内存的Image对象句柄,通过SystemTable可获得UEFI提供的各种服务。
UEFI Images即UEFI镜像,是UEFI规范的包含可执行代码的二进制程序文件,分为UEFI应用程序和UEFI驱动,采用PE32文件结构。UEFI Services即UEFI服务,是平台调用接口集合,允许UEFI程序和操作系统调用,这些接口由UEFI应用程序、驱动和UEFI OS Loader提供,涉及运行时服务RS和启动服务BS。
UefiMain型INF文件内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 [Defines] INF_VERSION = 0 x00010005 BASE_NAME = Uefi_Main FILE_GUID = 6937936 E-ED34-44 ab-AE97-1 FA5E7ED2116 MODULE_TYPE = UEFI_APPLICATION VERSION_STRING = 1.0 ENTRY_POINT = UefiMain [Sources] Uefi_Main.c [Packages] MdePkg/MdePkg.dec MdeModulePkg/MdeModulePkg.dec [LibraryClasses] UefiApplicationEntryPoint UefiLib
常用的头文件有:
库函数
内容
Uefi.h
基本数据类型和核心数据结构
Library/UefiLib.h
通用库函数,时间、简单锁、任务优先级、驱动管理、字符图形显示输出等
Library/BaseLib.h
字符串处理、数学、文件路径处理等
Library/BaseMemoryLib.h
内存库函数,内存拷贝、填充、清空等
Library/DebugLib.h
调试输出库函数
SystemTable结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 typedef struct { EFI_TABLE_HEADER Hdr; CHAR16 *FirmwareVendor; UINT32 FirmwareRevision; EFI_HANDLE ConsoleInHandle; EFI_SIMPLE_TEXT_INPUT_PROTOCOL *ConIn; EFI_HANDLE ConsoleOutHandle; EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *ConOut; EFI_HANDLE StandardErrorHandle; EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *StdErr; EFI_RUNTIME_SERVICES *RuntimeServices; EFI_BOOT_SERVICES *BootServices; UINTN NumberOfTableEntries; EFI_CONFIGURATION_TABLE *ConfigurationTable; } EFI_SYSTEM_TABLE;
对于ShellAppMain型的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <Uefi.h> #include <Library/UefiLib.h> #include <Library/ShellCEntryLib.h> #include <Library/UefiBootServicesTableLib.h> #include <Library/UefiRuntimeServicesTableLib.h> INTN EFIAPI ShellAppMain (IN UINTN Argc, IN CHAR16** Argv) { EFI_TIME curTime; Print (L"Hello,this is Entry of ShellAppMain!\n" ); gBS->Stall (2000 ); gRT->GetTime (&curTime, NULL ); Print (L"Current Time: %d-%d-%d %02d:%02d:%02d\n" , curTime.Year, curTime.Month, curTime.Day, curTime.Hour, curTime.Minute, curTime.Second); gST->ConOut->OutputString (gST->ConOut, L"Test SystemTable...\n\r" ); return (0 ); }
相比UefiMain型,ShellAppMain型能接收命令行参数。该型INF文件格式为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 [Defines] INF_VERSION = 0 x00010006 BASE_NAME = ShellApp_Main FILE_GUID = a912f198-7 f0e-4813 -b918-b757b106ec83 MODULE_TYPE = UEFI_APPLICATION VERSION_STRING = 0.1 ENTRY_POINT = ShellCEntryLib [Sources] ShellApp_Main.c [Packages] MdePkg/MdePkg.dec ShellPkg/ShellPkg.dec [LibraryClasses] UefiLib ShellCEntryLib
EDK2-LIBC工程允许UEFI程序直接使用C标准库,并使用main函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <Uefi.h> #include <Library/UefiLib.h> #include <Library/ShellCEntryLib.h> #include <Library/UefiBootServicesTableLib.h> #include <Library/UefiRuntimeServicesTableLib.h> #include <stdio.h> #include <stdlib.h> int main (IN int Argc, IN char ** Argv) { EFI_TIME curTime; printf ("Hello,this is Entry of main!\n" ); gBS->Stall (2000 ); gRT->GetTime (&curTime, NULL ); printf ("Current Time: %d-%d-%d %02d:%02d:%02d\n" , curTime.Year, curTime.Month, curTime.Day, curTime.Hour, curTime.Minute, curTime.Second); gST->ConOut->OutputString (gST->ConOut, L"Test SystemTable...\n\r" ); return 0 ; };
Stdlib型INF文件格式为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 [Defines] INF_VERSION = 0 x00010006 BASE_NAME = Stdlib_Main FILE_GUID = 4 ea97c46-1491 -4 dfd-b412-747010 f31e5f MODULE_TYPE = UEFI_APPLICATION VERSION_STRING = 0.1 ENTRY_POINT = ShellCEntryLib [Sources] Stdlib_Main.c [Packages] StdLib/StdLib.dec MdePkg/MdePkg.dec ShellPkg/ShellPkg.dec [LibraryClasses] LibC LibStdio
库模块 对于库模块的编写,INF文件格式为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 [Defines] INF_VERSION = 0 x00010005 BASE_NAME = MyLibraryLib FILE_GUID = 6937936 E-ED24-443 b-AEe7-1 FA5E7ED21A6 MODULE_TYPE = BASE VERSION_STRING = 1.0 LIBRARY_CLASS = MyLibraryLib CONSTRUCTOR = MyLibConstructor DESTRUCTOR = MyLibDestructor [Sources] MyLibrary.c MyLibrary.h [Packages] MdePkg/MdePkg.dec MdeModulePkg/MdeModulePkg.dec [LibraryClasses] UefiLib
库源码为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #ifndef _MYLIBRARY_H #define _MYLIBRARY_H #include <Uefi.h> VOID LibFunction (VOID) ; #endif #include <Uefi.h> #include <Library/UefiLib.h> VOID LibFunction (VOID) { Print (L"LibFunction() is called!\n" ); }; RETURN_STATUS EFIAPI MyLibConstructor (VOID ) { Print (L"MyLibConstructor() is called!\n" ); }; RETURN_STATUS EFIAPI MyLibDestructor (VOID) { Print (L"MyLibConstructor() is called!\n" ); };
对于应用本库的工程,INF文件格式为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 [Defines] INF_VERSION = 0 x00010006 BASE_NAME = MyLibApp FILE_GUID = 4 ea92c46-1491 -4 dfd-c412-747013 f31e5f MODULE_TYPE = UEFI_APPLICATION VERSION_STRING = 0.1 ENTRY_POINT = ShellCEntryLib [Sources] MyLibApp.c [Packages] StdLib/StdLib.dec MdePkg/MdePkg.dec ShellPkg/ShellPkg.dec [LibraryClasses] LibC LibStdio MyLibraryLib
该应用源码为:
1 2 3 4 5 6 7 8 #include <Uefi.h> #include <Library/UefiLib.h> #include <Library/ShellCEntryLib.h> #include <../MyLibrary/MyLibrary.h> int main (IN int Argc,IN char **Argv) { LibFunction (); return 0 ; };
然后AppPkg包的DSC文件还要添加:
1 2 3 4 5 [Components] AppPkg/Applications/LibSample/MyLibApp/MyLibApp.inf{ <LibraryClasses> MyLibrary|AppPkg/Applications/LibSample/MyLibrary/MyLibrary.inf }
其他工程文件 DEC文件配合DSC文件,描述包的公开数据和接口。IDF、UNI和VFR为资源文件,提供图像、文字和框架资源。DSC、DEC和INF文件配合源代码和资源文件,用Build工具生成.efi、.lib等二进制文件。FDF文件用于生成Option ROM镜像、固件镜像和可启动镜像,与DSC文件和二进制文件用GenFw工具生成镜像,如OVMF镜像。
UEFI Protocol 可通过启动服务的HandleProtocol
或OpenProtocol
找到设备句柄,句柄与Protocol接口联系在一起,可通过Protocol接口的函数指针访问Protocol接口函数。每个Protocol都有GUID、Protocol接口结构体和Protocol服务。下面是个例子:
1 2 3 4 5 6 7 8 9 10 11 12 #define EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID{0x9042a9de, 0x23dc, 0x4a38, {0x96, 0xfb, 0x7a, 0xde, 0xd0, 0x80, 0x51, 0x6a}} typedef struct _EFI_GRAPHICS_OUTPUT_PROTOCOL EFI_GRAPHICS_OUTPUT_PROTOCOL;struct _EFI_GRAPHICS_OUTPUT_PROTOCOL { EFI_GRAPHICS_OUTPUT_PROTOCOL_QUERY_MODE QueryMode; EFI_GRAPHICS_OUTPUT_PROTOCOL_SET_MODE SetMode; EFI_GRAPHICS_OUTPUT_PROTOCOL_BLT Blt; EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE *Mode; }; typedef EFI_STATUS (EFIAPI *EFI_GRAPHICS_OUTPUT_PROTOCOL_SET_MODE) ( IN EFI_GRAPHICS_OUTPUT_PROTOCOL *This, IN UINT32 ModeNumber ) ;
使用Protocol步骤如下:
在UEFI规范中找到需要用的Protocol的GUID,用OpenProtocol
、HandleProtocol
或LocateProtocol
找出Protocol实例,若多个设备支持此Protocol,则要用LocateHandleBuffer
或LocateHandle
列出所有句柄,再用OpenProtocol
等找出Protocol实例。
用该Protocol提供的接口函数来实现所需功能。
用CloseProtocol
关闭打开的Protocol实例。
OpenProtocol
如下,对于给定句柄查询是否支持指定Protocol,支持则打开对应Protocol实例,在入口参数中保存对象指针并返回EFI_SUCCESS,不支持则返回相应错误代码。若Handle的Protocol链表中有该Protocol,则Protocol实例指针将写到*Interface
中。UEFI驱动和应用中的该函数参数含义有所不同。UEFI驱动下ControllerHandle是拥有该驱动的控制器,AgentHandle是拥有该EFI_DRIVER_BINDING_PROTOCOL实例的句柄。UEFI应用下ControllerHandle可忽略,AgentHandle是该程序句柄,即UefiMain函数第一个参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 typedef 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 OPTIONAL, IN UINT32 Attributes ) ;#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
例如打开XyzIo:
1 2 3 4 5 6 7 EFI_BOOT_SERVICES* gBS; EFI_HANDLE ImageHandle; 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);
上面gEfiXyzIoProtocol是EFI_XYZ_IO_PROTOCOL型Protocol相关的GUID,值一般定义在DEC文件中,UEFI应用或驱动应在INF文件中声明此GUID。
HandleProtocol
是OpenProtocol
的简化版本:
1 2 3 4 5 6 7 8 9 typedef EFI_STATUS (EFIAPI *EFI_HANDLE_PROTOCOL) ( IN EFI_HANDLE Handle, IN EFI_GUID *Protocol, OUT VOID **Interface ) ;
LocateProtocol
不用设备句柄,自己在系统中找句柄列表,返回找到的第一个:
1 2 3 4 5 6 7 typedef EFI_STATUS (EFIAPI *EFI_LOCATE_PROTOCOL) ( IN EFI_GUID *Protocol, IN VOID *Registration OPTIONAL, OUT VOID **Interface ) ;
一般用法如:
1 2 EFI_GRAPHICS_OUTPUT_PROTOCOL* gGraphicsOutput; gBS->LocateProtocol (&gEfiGraphicsOutputProtocolGuid, NULL , &gGraphicsOutput);
用LocateHandle
或LocateHandleBuffer
通过GUID找设备句柄。前者缓冲区需要调用者准备,后者内部实现,但两者内存释放都要调用者完成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 typedef EFI_STATUS (EFIAPI *EFI_LOCATE_HANDLE) ( IN EFI_LOCATE_SEARCH_TYPE SearchType, IN EFI_GUID *Protocol OPTIONAL, IN VOID *SearchKey OPTIONAL, IN OUT UINTN *BufferSize, OUT EFI_HANDLE *Buffer ) ;typedef EFI_STATUS (EFIAPI *EFI_LOCATE_HANDLE_BUFFER) ( IN EFI_LOCATE_SEARCH_TYPE SearchType, IN EFI_GUID *Protocol OPTIONAL, IN VOID *SearchKey OPTIONAL, OUT UINTN *NoHandles, OUT EFI_HANDLE **Buffer ) ;typedef enum { AllHandles, ByRegisterNotify, ByProtocol } EFI_LOCATE_SEARCH_TYPE;
例如找出所有串口Protocol实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 EFI_STATUS Status; UINTN BufferSize = 0 ; UINTN Index; EFI_HANDLE* HandleBuffer = NULL ; EFI_SERIAL_IO_PROTOCOL* gSerialIoArray[256 ]; Status = gBS->LocateHandle (ByProtocol, &gEfiSerialIoProtocolGuid, NULL , &BufferSize, HandleBuffer); if (Status == EFI_BUFFER_TOO_SMALL) { HandleBuffer = AllocateZeroPool (BufferSize); if (HandleBuffer == NULL ) return EFI_OUT_OF_RESOURCES; Status = gBS->LocateHandle (ByProtocol, &gEfiSerialIoProtocolGuid, NULL , &BufferSize, HandleBuffer); for (Index = 0 ; Index < BufferSize / sizeof (EFI_HANDLE); Index++) Status = gBS->HandleProtocol (HandleBuffer[Index], &gEfiSerialIoProtocolGuid, (VOID**)&(gSerialIoArray[Index])); if (HandleBuffer != NULL ) Status = gBS->FreePool (HandleBuffer); }; EFI_STATUS Status; EFI_HANDLE* SerialHandleBuffer = NULL ; UINTN HandleIndex = 0 ; UINTN HandleCount = 0 ; EFI_SERIAL_IO_PROTOCOL* gSerialIOArray[256 ]; Status = gBS->LocateHandleBuffer (ByProtocol, &gEfiSerialIoProtocolGuid, NULL , &HandleCount, &SerialHandleBuffer); if (EFI_ERROR (Status))return Status;nSerialIO = HandleCount; for (HandleIndex = 0 ; HandleIndex < HandleCount; HandleIndex++) { Status = gBS->HandleProtocol (SerialHandleBuffer[HandleIndex], &gEfiSerialIoProtocolGuid, (VOID**)&(gSerialIOArray[HandleIndex])); if (EFI_ERROR (Status)) continue ; }; if (SerialHandleBuffer != NULL )Status = gBS->FreePool (SerialHandleBuffer);
用OpenProtocolInformatin
获得Protocol实例详细信息。每次Protocol被打开,都添加一项该结构,若有相同项则直接OpenCount加一。关闭时减一直至删除。
1 2 3 4 5 6 7 8 9 10 11 12 13 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;
用完Protocol后用CloseProtocol
关闭:
1 2 3 4 5 6 7 typedef EFI_STATUS (EFIAPI *EFI_CLOSE_PROTOCOL) ( IN EFI_HANDLE Handle, IN EFI_GUID *Protocol, IN EFI_HANDLE AgentHandle, IN EFI_HANDLE ControllerHandle ) ;
例如Protocol常用的使用方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 #include <Uefi.h> #include <Library/UefiLib.h> #include <Protocol/SerialIo.h> #include <Library/DebugLib.h> #include <Library/BaseMemoryLib.h> #include <Library/UefiBootServicesTableLib.h> #include <Library/BaseLib.h> #include <Protocol/PciIo.h> #include <Protocol/PciRootBridgeIo.h> #include <IndustryStandard/Pci.h> #include <Protocol/Rng.h> #include <Protocol/SimpleFileSystem.h> #include <IndustryStandard/Bmp.h> EFI_STATUS ListProtocolMsg (IN EFI_GUID* ProtocolGuid, OUT VOID** Interface) ;EFI_STATUS EFIAPI UefiMain (IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE* SystemTable) { EFI_RNG_PROTOCOL* gRNGOut; EFI_SERIAL_IO_PROTOCOL* gSerialIO; gST->ConOut->SetAttribute (gST->ConOut, EFI_BACKGROUND_BLACK | EFI_RED); Print ((const CHAR16*)L"Action: SerialIoProtocol\n" ); ListProtocolMsg (&gEfiSerialIoProtocolGuid, (VOID**)&gSerialIO); gST->ConOut->SetAttribute (gST->ConOut, EFI_BACKGROUND_BLACK | EFI_CYAN); Print ((const CHAR16*)L"Action: RngProtocol\n" ); ListProtocolMsg (&gEfiRngProtocolGuid, (VOID**)&gRNGOut); gST->ConOut->SetAttribute (gST->ConOut, EFI_BACKGROUND_BLACK | EFI_LIGHTGRAY); return EFI_SUCCESS; } EFI_STATUS ListProtocolMsg (IN EFI_GUID* ProtocolGuid, OUT VOID** Interface) { EFI_STATUS Status; EFI_HANDLE* myHandleBuff = NULL ; UINTN HandleCount = 0 ; UINTN i; EFI_OPEN_PROTOCOL_INFORMATION_ENTRY* InfEntryArray; UINTN InfEntryCount; Print ((const CHAR16*)L" GUID: {0x%08x, 0x%04x, 0x%04x, {" , ProtocolGuid->Data1, ProtocolGuid->Data2, ProtocolGuid->Data3); for (i = 0 ; i < 8 ; i++) Print ((const CHAR16*)L" 0x%02x" , ProtocolGuid->Data4[i]); Print ((const CHAR16*)L"}}\n" ); Status = gBS->LocateHandleBuffer (ByProtocol, ProtocolGuid, NULL , &HandleCount, &myHandleBuff); if (EFI_ERROR (Status)) { Print ((const CHAR16*)L"Not Found Handle!\n" ); return Status; } else Print ((const CHAR16*)L"Found Handle Count: %d\n" , HandleCount); for (i = 0 ; i < HandleCount; i++) { Status = gBS->HandleProtocol (myHandleBuff[i], ProtocolGuid, Interface); if (EFI_ERROR (Status)) continue ; else break ; } Status = gBS->OpenProtocolInformation (myHandleBuff[i], ProtocolGuid, &InfEntryArray, &InfEntryCount); if (EFI_ERROR (Status)) Print ((const CHAR16*)L"Not Get the Protocol's information!\n" ); else { Print ((const CHAR16*)L"EntryCount=%d \n" , InfEntryCount); gBS->CloseProtocol (myHandleBuff[i], ProtocolGuid, InfEntryArray->AgentHandle, InfEntryArray->ControllerHandle); if (InfEntryArray) gBS->FreePool (InfEntryArray); } if (myHandleBuff) gBS->FreePool (myHandleBuff); return Status; }