UEFI学习笔记(5)——Protocol的使用

UEFI Protocol 是 UEFI 固件中定义的一套标准硬件接口规范,相当于为电脑各个部件制定的"通用语言"。​比如:

  1. 开机画面显示​​:系统通过 Graphics Output Protocol 与显卡对话,就向显示器通过 HDMI 接口接收信号。
  2. 键盘输入检测​​:Simple Input Protocol 让系统识别键盘按键,就向 USB 接口接收键盘输入。
  3. ​​硬盘数据读取​​:Block I/O Protocol 提供访问硬盘的能力,就向 SATA 接口连接硬盘。
  4. 网络连接​​:TCP/IP Protocol 实现网络通信,就向网卡通过 RJ45 接口联网。

这些 Protocol 就像电脑主板上的各种标准化接口(USB/HDMI/SATA等),让不同厂商的硬件都能即插即用,不需要为每个设备单独开发驱动。

1. 引言

1.1 UEFI Protocol概述

UEFI Protocol是UEFI规范定义的核心接口机制,为预启动环境中的硬件抽象和组件交互提供标准化方法。与Linux设备模型相比:

特性UEFI ProtocolLinux设备模型
设计目标预启动环境硬件抽象操作系统运行时设备管理
接口形式基于GUID的C语言接口基于文件系统的设备节点
发现机制Handle Databasesysfs和设备树
权限控制简单权限模型复杂权限系统

2. Protocol核心知识点

每个Protocol都包含了三部分内容:GUID、Protocol接口的结构体和Protocol服务(即Protocol的接口函数),使用结构体的方式,使用函数栀指针作为结构体的成员变量,提供给用户使用。

  • ​​GUID​​:全局唯一标识符(如串口Protocol的EFI_SERIAL_IO_PROTOCOL_GUID)
  • 结构体​​:包含函数指针和数据的接口定义(如EFI_SERIAL_IO_PROTOCOL
  • 服务函数​​:结构体中的函数指针成员(如Read/Write)

例如在MdePkg/Include/Protocol/SerialIo.h文件中就是一个串口的 Protocol 实例,代码清单如下:

#define EFI_SERIAL_IO_PROTOCOL_GUID \
  { \
    0xBB25CF6F, 0xF1D4, 0x11D2, {0x9A, 0x0C, 0x00, 0x90, 0x27, 0x3F, 0xC1, 0xFD } \
  }
struct _EFI_SERIAL_IO_PROTOCOL {
  UINT32                         Revision;
  EFI_SERIAL_RESET               Reset;
  EFI_SERIAL_SET_ATTRIBUTES      SetAttributes;
  EFI_SERIAL_SET_CONTROL_BITS    SetControl;
  EFI_SERIAL_GET_CONTROL_BITS    GetControl;
  EFI_SERIAL_WRITE               Write;
  EFI_SERIAL_READ                Read;
  EFI_SERIAL_IO_MODE             *Mode;
  CONST EFI_GUID                 *DeviceTypeGuid; // Revision 1.1
};

从结构体里面可以看出,结构体具有包括写串口、读串口、设置串口模式等函数,这些都是函数指针,已其中的成员变量Read来进行举例,代码清代如下:

EFI_STATUS
EFIAPI
SerialRead (
  IN EFI_SERIAL_IO_PROTOCOL  *This,
  IN OUT UINTN               *BufferSize,
  OUT VOID                   *Buffer
  );

SerialRead 函数中的this是指向EFI_SERIAL_IO_PROTOCOL 的this指针。所有的Protocol的第一个参数都是指向Protocol实例的指针。例如UEFI串口在访问的时候,与哪一个串口进行通行,就是使用这个参数来决定。这里的this 指针需要程序员自己手动添加。

3. 如何使用Protocol

使用Protocol一般需要有三个步骤:

  • 找到Protocol实例;
  • 使用这个Protocol提供的接口函数来实现所需要的功能;
  • 最后使用CloseProtocol关闭打开的Protocol实例。

3.1 打开服务的方法

3.1.1 OpenProtocol()函数

OpenProtocol()是UEFI Boot Services提供的关键函数,用于获取特定设备句柄上的Protocol接口并建立使用关系。该函数的主要功能包括:

  • 功能定位​​:通过指定GUID和句柄,获取对应的Protocol接口实例;
  • 权限管理​​:控制对Protocol的访问权限,确保安全使用
  • ​​引用计数​​:维护Protocol的使用计数,防止资源冲突
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,
  IN  UINT32                    Attributes
  );

参数说明​​:

  • Handle​​:目标设备句柄
  • Protocol​​:要打开的Protocol的GUID
  • ​​Interface​​:返回的Protocol接口指针
  • ​​AgentHandle​​:调用者标识(通常是ImageHandle)
  • ​​ControllerHandle​​:控制器句柄(可选)
  • Attributes​​:打开属性标志,控制访问方式

常用属性标志​​:

  • EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL:标准打开方式
  • EFI_OPEN_PROTOCOL_GET_PROTOCOL:仅获取接口
  • EFI_OPEN_PROTOCOL_EXCLUSIVE:独占访问

如果Handle设备的链表中有这个Protocol,则Protocol实例的指针将写到*Interface中。

3.1.2 HandleProtocol()函数

HandleProtocol()是UEFI Boot Services提供的Protocol获取函数,用于从指定设备句柄上直接获取Protocol接口实例。

主要特点:

  • 直接访问:通过已知设备句柄快速获取Protocol
  • 简单易用:无需处理复杂的权限属性
  • 无引用计数:不增加Protocol的使用计数

函数原型:

typedef
EFI_STATUS
(EFIAPI *EFI_HANDLE_PROTOCOL)(
  IN  EFI_HANDLE                Handle,
  IN  EFI_GUID                  *Protocol,
  OUT VOID                      **Interface
  );

参数说明:

  • Handle:目标设备句柄
  • Protocol:要获取的Protocol的GUID
  • Interface:返回的Protocol接口指针

返回值:

  • EFI_SUCCESS:成功获取Protocol
  • EFI_UNSUPPORTED:句柄不支持该Protocol
  • EFI_INVALID_PARAMETER:参数无效

3.1.3 LocateProtocol()函数

LocateProtocol()是UEFI Boot Services提供的全局Protocol查找函数,用于在系统中查找第一个匹配指定GUID的Protocol实例。
主要特点:

  • 全局查找:搜索整个系统已安装的Protocol
  • 快速访问:无需知道具体设备句柄
  • 返回首个匹配:只返回第一个找到的Protocol实例

函数原型:

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

参数说明:

  • Protocol:要查找的Protocol的GUID
  • Registration:可选注册键(通常为NULL)
  • Interface:返回的Protocol接口指针

返回值:

  • EFI_SUCCESS:成功找到Protocol
  • EFI_NOT_FOUND:未找到匹配的Protocol
  • EFI_INVALID_PARAMETER:参数无效
特性OpenProtocolHandleProtocolLocateProtocol
获取方式通过句柄+属性直接通过句柄全局搜索
权限控制支持(Attributes)不支持不支持
引用计数维护不维护不维护
使用场景需要精细控制时已知句柄快速访问查找全局Protocol
性能消耗中等高(需遍历)

选择建议:

  • 需要精细控制时使用OpenProtocol(),在UEFI规范中,建议使用OpenProtocol。
  • 已知具体句柄时使用HandleProtocol()
  • 仅需查找任意实例时使用LocateProtocol()

对于Protocol的查找都是通过一个Handle和Protocol关系图来进行的,但是现在有点搞不懂,就先不写了,后面搞懂了在写,这是教材上面的一张图。
在这里插入图片描述

4.代码实例

代码源于《UEFI编程实践》这本书上面,代码说明:

  1. 使用LocateHandleBuffer()函数,得到指定Protocol GUID的所有设备句柄。
  2. 通过HandleProtocol()函数得到最先扫描的设备句柄的Protocol指针。
  3. 通过OpenProtocolInformation()函获取最先扫描的设备句柄的Protocol信息。
  4. 调用CloseProtocol()函数关闭Protocol,并回收被调用函数申请的内存。

4.1 使用Protocol的通用模板:

#include <Uefi.h>
#include <Library/UefiBootServicesTableLib.h>

/**
 * 通用 Protocol 使用函数
 * 
 * @param ProtocolGuid    要使用的 Protocol 的 GUID
 * @param ProtocolInstance 返回的 Protocol 实例指针
 * @param Handle          可选,指定要从哪个设备句柄获取 Protocol
 * 
 * @return EFI_STATUS
 */
EFI_STATUS UseGenericProtocol(
    IN EFI_GUID *ProtocolGuid,
    OUT VOID **ProtocolInstance,
    IN EFI_HANDLE Handle OPTIONAL
)
{
    EFI_STATUS Status;
    
    if (ProtocolGuid == NULL || ProtocolInstance == NULL) {
        return EFI_INVALID_PARAMETER;
    }

    // 方法1: 如果有特定句柄,使用 OpenProtocol
    if (Handle != NULL) {
        Status = gBS->OpenProtocol(
            Handle,
            ProtocolGuid,
            ProtocolInstance,
            gImageHandle,  // 使用当前镜像的句柄
            NULL,
            EFI_OPEN_PROTOCOL_GET_PROTOCOL
        );
    } 
    // 方法2: 没有特定句柄,使用 LocateProtocol
    else {
        Status = gBS->LocateProtocol(
            ProtocolGuid,
            NULL,
            ProtocolInstance
        );
    }

    if (EFI_ERROR(Status)) {
        DEBUG((EFI_D_ERROR, "Failed to get protocol: %r\n", Status));
        return Status;
    }

    if (*ProtocolInstance == NULL) {
        DEBUG((EFI_D_ERROR, "Protocol instance is NULL\n"));
        return EFI_NOT_FOUND;
    }

    return EFI_SUCCESS;
}

/**
 * 使用 Protocol 的示例
 */
EFI_STATUS ExampleProtocolUsage()
{
    EFI_STATUS Status;
    VOID *Protocol = NULL;
    
    // 1. 获取 Protocol 实例
    Status = UseGenericProtocol(
        &gEfiSomeProtocolGuid,  // 替换为实际的 Protocol GUID
        &Protocol,
        NULL  // 不指定特定句柄
    );
    
    if (EFI_ERROR(Status)) {
        return Status;
    }
    
    // 2. 将通用指针转换为具体 Protocol 类型
    EFI_SOME_PROTOCOL *SomeProtocol = (EFI_SOME_PROTOCOL *)Protocol;
    
    // 3. 使用 Protocol 提供的方法
    // 例如: SomeProtocol->SomeFunction(SomeProtocol, ...);
    
    return EFI_SUCCESS;
}

4.2 ProtocolStudy.c

#include "ProtocolStudy.h"

/**
 * @brief 列出指定Protocol的信息并获取接口实例
 * @param ProtocolGuid 要查询的Protocol的GUID
 * @param Interface 返回获取到的Protocol接口指针
 * @return EFI_STATUS 执行状态码
 */
EFI_STATUS ListProtocolMsg(IN EFI_GUID *ProtocolGuid, OUT VOID **Interface)
{
    EFI_STATUS Status;
    EFI_HANDLE *myHandleBuff = NULL;                    // 存储找到的设备句柄数组
    UINTN i, HandleCount = 0;                           // 句柄计数器和总数
    EFI_OPEN_PROTOCOL_INFORMATION_ENTRY *InfEnterArray; // Protocol打开信息数组
    UINTN InfEnterCount;                                // Protocol打开信息条目数

    // 打印Protocol的GUID信息(格式:{Data1,Data2,Data3 Data4})
    Print(L"GUID:{0x%08x,0x%04x,%0x04x}", ProtocolGuid->Data1, ProtocolGuid->Data2, ProtocolGuid->Data3);
    for (UINT8 i = 0; i < 8; i++)
    {
        Print(L" 0x%02x", ProtocolGuid->Data4[i]); // 打印GUID的Data4部分
    }
    Print(L"\n");

    // 步骤1:通过Protocol GUID查找所有支持该Protocol的设备句柄
    Status = gBS->LocateHandleBuffer(
        ByProtocol,   // 按Protocol类型查找
        ProtocolGuid, // 目标Protocol的GUID
        NULL,         // 无额外搜索条件
        &HandleCount, // 返回找到的句柄数量
        &myHandleBuff // 返回句柄数组指针
    );

    if (EFI_ERROR(Status))
    {
        Print(L"Not Found Handle\n"); // 未找到任何支持该Protocol的句柄
        return Status;
    }
    else
    {
        Print(L"Found Handle Count:%d\n", HandleCount); // 打印找到的句柄数量
    }

    // 步骤2:遍历找到的句柄,尝试获取Protocol接口
    for (i = 0; i < HandleCount; i++)
    {
        // 尝试从当前句柄获取Protocol接口
        Status = gBS->HandleProtocol(
            myHandleBuff[i], // 当前设备句柄
            ProtocolGuid,    // Protocol的GUID
            Interface        // 返回的Protocol接口指针
        );

        if (!EFI_ERROR(Status))
        {
            break; // 成功获取到接口,退出循环
        }
    }

    // 步骤3:获取并打印Protocol的详细信息
    Status = gBS->OpenProtocolInformation(
        myHandleBuff[i], // 目标设备句柄
        ProtocolGuid,    // Protocol的GUID
        &InfEnterArray,  // 返回打开信息数组
        &InfEnterCount   // 返回信息条目数
    );

    if (EFI_ERROR(Status))
    {
        Print(L"NOT GET the Protocol's information!\n"); // 获取信息失败
    }
    else
    {
        Print(L"EntryCount = %d \n", InfEnterCount); // 打印打开信息条目数

        // 关闭Protocol(释放资源)
        gBS->CloseProtocol(
            myHandleBuff[i],                // 设备句柄
            ProtocolGuid,                   // Protocol的GUID
            InfEnterArray->AgentHandle,     // 调用者句柄
            InfEnterArray->ControllerHandle // 控制器句柄
        );

        if (InfEnterArray)
        {
            gBS->FreePool(InfEnterArray); // 释放打开信息数组内存
        }
    }

    // 释放句柄数组内存
    if (myHandleBuff)
    {
        gBS->FreePool(myHandleBuff);
    }

    return Status;
}

/**
 * @brief UEFI应用程序入口函数
 * @param ImageHandle 应用程序镜像句柄
 * @param SystemTable 系统表指针
 * @return EFI_STATUS 执行状态码
 */
EFI_STATUS EFIAPI UefiMain(IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable)
{
    EFI_RNG_PROTOCOL *gRNGOut;         // 随机数Protocol接口指针
    EFI_SERIAL_IO_PROTOCOL *gSerialIO; // 串口Protocol接口指针

    // 设置控制台输出颜色为红底黑字
    gST->ConOut->SetAttribute(gST->ConOut, EFI_BACKGROUND_BLACK | EFI_RED);

    // 查找并列出串口Protocol信息
    Print(L"Action:SerialProtocol\n");
    ListProtocolMsg(&gEfiSerialIoProtocolGuid, (VOID **)&gSerialIO);

    // 查找并列出随机数Protocol信息
    Print(L"Action:RngProtocol\n");
    ListProtocolMsg(&gEfiRngProtocolGuid, (VOID **)&gRNGOut);

    // 恢复控制台默认颜色(黑底灰字)
    gST->ConOut->SetAttribute(gST->ConOut, EFI_BACKGROUND_BLACK | EFI_LIGHTGRAY);

    return EFI_SUCCESS; // 返回成功状态
}

4.3 ProtocolStudy.h

#ifndef __PROTOCOLSTUDY_H
#define __PROTOCOLSTUDY_H

#include <Uefi.h>
#include <Library/UefiApplicationEntryPoint.h>
#include <Library/UefiLib.h>
#include <Protocol/Rng.h>
#include <Protocol/SerialIo.h>
#include <Library/UefiBootServicesTableLib.h>


#endif

4.4 ProtocolStudy.inf

[Defines]
    INF_VERSION = 0x00010006
    BASE_NAME = ProtocolStudy
    FILE_GUID = 31e990f8-918c-45db-9736-9205126eaa83
    MODULE_TYPE = UEFI_APPLICATION
    VERSION_STRING = 1.0
    ENTRY_POINT = UefiMain

[Sources]
    ProtocolStudy.c

[Packages]
    MdePkg/MdePkg.dec

[LibraryClasses]
    UefiApplicationEntryPoint
    UefiLib
    UefiBootServicesTableLib

[Protocols]
  gEfiSerialIoProtocolGuid       # 声明依赖的SerialIo协议
  gEfiRngProtocolGuid            # 声明依赖的RNG协议
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值