如何通过UEFI Application 调用特定Protocol实现复杂功能?

摘要:如何在 UEFI 应用程序中调用 Protocol 前面我们介绍过 Protocol 的本质是一个包含一系列函数指针的 C 语言结构体。例如: typedef struct { EFI_TEXT_RESET Reset; EFI_TEXT_
如何在 UEFI 应用程序中调用 Protocol 前面我们介绍过 Protocol 的本质是一个包含一系列函数指针的 C 语言结构体。例如: typedef struct { EFI_TEXT_RESET Reset; EFI_TEXT_OUTPUT_STRING OutputString; EFI_TEXT_TEST_STRING TestString; EFI_TEXT_QUERY_MODE QueryMode; // ... 更多函数指针 EFI_SIMPLE_TEXT_OUTPUT_MODE *Mode; // 这是一个数据指针,指向协议的状态信息 } EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL; 本文旨在说明 Protocol 中定义的函数在 UEFI 应用程序中该如何调用。 示例程序 我们以图形输出协议 GOP 为例,编写代码演示应用程序如何查找指定协议。 我们知道,Handle 表示某个对象的实例,Protocol 通常挂在对应的 Handle 上,表示这个 Handle 所具备的功能。比如硬盘这个 Handle,就需要有块读写的功能,对应 Block IO Protocol。 本示例代码的逻辑是,首先根据协议的 ProtocolGuid 查找支持这个协议的 Handle,注意可能有多个 Handle 支持这个协议。拿到句柄之后,需要获取具体的协议接口随后调用函数。 我们介绍本文会用到的 Boot Service 中的两个关键函数: LocateHandleBuffer/LocateHandle 这两个函数都用于在 UEFI 系统中查找句柄,区别在于内存管理方式。 typedef EFI_STATUS (EFIAPI *EFI_LOCATE_HANDLE)( IN EFI_LOCATE_SEARCH_TYPE SearchType, // 搜索方式 IN EFI_GUID *Protocol OPTIONAL, // 要查找的协议GUID IN VOID *SearchKey OPTIONAL, // 搜索键(与SearchType相关) 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 // 输出:分配的缓冲区指针 ); LocateHandle 调用者自己分配缓冲区,使用流程为: 第一次调用:传小BufferSize,函数返回EFI_BUFFER_TOO_SMALL并填充所需大小 你根据返回的大小手动分配内存 第二次调用:传入足够大的缓冲区,获取句柄列表 UINTN BufferSize = 0; EFI_HANDLE *Buffer; // 第一次调用获取大小 LocateHandle(ByProtocol, &gEfiDriverBindingProtocolGuid, NULL, &BufferSize, NULL); // 分配内存 Buffer = AllocatePool(BufferSize); // 第二次调用获取实际句柄 LocateHandle(ByProtocol, &gEfiDriverBindingProtocolGuid, NULL, &BufferSize, Buffer); LocateHandleBuffer函数内部自动分配缓冲区 直接返回NoHandles(句柄数量)和Buffer(已分配好的缓冲区指针) 使用完毕后需要调用FreePool()释放 UINTN NoHandles; EFI_HANDLE *Buffer; // 一次调用完成 LocateHandleBuffer(ByProtocol, &gEfiDriverBindingProtocolGuid, NULL, &NoHandles, &Buffer); // 使用Buffer... FreePool(Buffer); // 别忘了释放 OpenProtocol OpenProtocol 用于获取协议接口函数,打开一个已安装的协议,获得其函数指针或数据结构的访问权。 typedef EFI_STATUS (EFIAPI *EFI_OPEN_PROTOCOL)( IN EFI_HANDLE Handle, // 协议所在的句柄 IN EFI_GUID *Protocol, // 协议的GUID OUT VOID **Interface OPTIONAL, // 输出:协议接口指针 IN EFI_HANDLE AgentHandle, // 请求者句柄(驱动映像句柄) IN EFI_HANDLE ControllerHandle, // 控制器句柄 IN UINT32 Attributes // 打开模式 ); Attributes(打开模式): 模式 含义 典型场景 EFI_OPEN_PROTOCOL_BY_DRIVER 驱动独占控制 驱动开始管理设备时 `EFI_OPEN_PROTOCOL_BY_DRIVER EFI_OPEN_PROTOCOL_EXCLUSIVE` 强制独占 EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER 子控制器打开 Bus驱动为子设备打开 EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL 简单获取(不建立关系) 临时读取信息 EFI_OPEN_PROTOCOL_GET_PROTOCOL 仅获取(不注册到协议数据库) 内部查询 简单使用示例: EFI_STATUS Status; EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *ConOut; // 从系统表句柄打开Console输出协议 Status = gBS->OpenProtocol( gST->ConsoleOutHandle, // 句柄 &gEfiSimpleTextOutProtocolGuid, // 协议GUID (VOID **)&ConOut, // 输出接口 gImageHandle, // 本驱动句柄 NULL, // 无控制器 EFI_OPEN_PROTOCOL_GET_PROTOCOL // 简单获取 ); OpenProtocol 与 HandleProtocol / LocateProtocol 的关系: HandleProtocol:是 OpenProtocol 的简化包装,使用 EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL 模式。 LocateProtocol:先查找句柄,再打开协议(两步合并)。直接查找系统中第一个支持指定 Protocol 的 Handle 并打开它。 OpenProtocol:最底层,可精确控制驱动与设备的关系(谁打开了谁),用于驱动的启动/停止管理。 OpenProtocol 的重要机制:协议引用计数。每次成功 OpenProtocol 会增加协议的引用计数,必须配对调用 CloseProtocol 来减少计数,当计数归零时协议才能被卸载。 示例代码: #include <Uefi.h> #include <Library/UefiLib.h> #include <Library/UefiBootServicesTableLib.h> EFI_STATUS EFIAPI UefiMain ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { EFI_STATUS Status; UINTN NoHandles; EFI_HANDLE *Buffer; EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput; // Locate all handles that support the Graphics Output Protocol Status = gBS->LocateHandleBuffer( ByProtocol, &gEfiGraphicsOutputProtocolGuid, NULL, &NoHandles, &Buffer ); Print(L"Status of LocateHandleBuffer: %r\n", Status); if (EFI_ERROR(Status)) { Print(L"Failed to locate handles for Graphics Output Protocol: %r\n", Status); return Status; } Print(L"Hello, Protocol!\n"); Print(L"Number of handles that support Graphics Output Protocol: %d\n", NoHandles); // Open the Graphics Output Protocol on the first handle found Status = gBS->OpenProtocol( Buffer[0], &gEfiGraphicsOutputProtocolGuid, (VOID**)&GraphicsOutput, ImageHandle, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL ); Print(L"Status of OpenProtocol: %r\n", Status); if (EFI_ERROR(Status)) { Print(L"Failed to open Graphics Output Protocol: %r\n", Status); return Status; } return EFI_SUCCESS; } 参考:https://www.bilibili.com/video/BV1a34y197YF?spm_id_from=333.788.videopod.sections&vd_source=2ee7caa81fced5c94d0d863e82c6acae Steady Progress!