UEFI Application 如何调用 Protocol

如何在 UEFI 应用程序中调用 Protocol

前面我们介绍过 Protocol 的本质是一个包含一系列函数指针的 C 语言结构体。例如:

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 中的两个关键函数:

  1. LocateHandleBuffer/LocateHandle

    这两个函数都用于在 UEFI 系统中查找句柄,区别在于内存管理方式。

    c 复制代码
    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并填充所需大小

    • 你根据返回的大小手动分配内存

    • 第二次调用:传入足够大的缓冲区,获取句柄列表

    c 复制代码
    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()释放
    c 复制代码
    UINTN NoHandles;
    EFI_HANDLE *Buffer;
    // 一次调用完成
    LocateHandleBuffer(ByProtocol, &gEfiDriverBindingProtocolGuid,
                       NULL, &NoHandles, &Buffer);
    // 使用Buffer...
    FreePool(Buffer);  // 别忘了释放
  2. OpenProtocol

    OpenProtocol 用于获取协议接口函数,打开一个已安装的协议,获得其函数指针或数据结构的访问权。

    c 复制代码
    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 仅获取(不注册到协议数据库) 内部查询

简单使用示例:

c 复制代码
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             // 简单获取
);

OpenProtocolHandleProtocol / LocateProtocol 的关系:

  • HandleProtocol:是 OpenProtocol 的简化包装,使用 EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL 模式。
  • LocateProtocol:先查找句柄,再打开协议(两步合并)。直接查找系统中第一个支持指定 Protocol 的 Handle 并打开它。
  • OpenProtocol:最底层,可精确控制驱动与设备的关系(谁打开了谁),用于驱动的启动/停止管理。

OpenProtocol 的重要机制:协议引用计数。每次成功 OpenProtocol 会增加协议的引用计数,必须配对调用 CloseProtocol 来减少计数,当计数归零时协议才能被卸载。

示例代码:

c 复制代码
#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!

相关推荐
阿源-20 小时前
嵌入式 - 在VMware中安装Ubuntu虚拟机
ubuntu·嵌入式
CinzWS1 天前
BASETIMER(基本定时器) - 系统的时基:从时钟源、分频链到定时中断的确定性追求
单片机·嵌入式·basetimer
lularible1 天前
PTP协议精讲(3.8):硬件时间戳详解——纳秒级精度的魔法
网络·网络协议·开源·嵌入式·ptp
FreakStudio2 天前
和做工厂系统的印尼老哥,复刻了一套属于 MicroPython 的包管理系统
python·单片机·嵌入式·大学生·面向对象·并行计算·电子diy·电子计算机
7yewh2 天前
电阻应变片的深度探究
嵌入式
CinzWS2 天前
A53低功耗验证:状态机验证与唤醒时序检查——芯片的“睡眠科学“
嵌入式·芯片验证·原型验证·a53
keibin9882 天前
UEFI规范到底规定了什么?五分钟读懂核心概念
嵌入式
CinzWS2 天前
DMA控制器 - 沉默的加速器:驾驭通道仲裁、传输握手与内存一致性的双刃剑
arm开发·dma·嵌入式
CinzWS2 天前
GIPC(处理器间通信) - 多核的桥梁:剖析硬件队列、门铃中断与共享内存的数据一致性困局
arm开发·嵌入式·gipc
阿源-3 天前
嵌入式-常见简单通信协议介绍
嵌入式