UEFI Application如何调用Protocol(二)

UEFI Application如何调用Protocol(二)

在上一篇文章中,我们介绍了UEFI Application调用Protocol的基本流程和核心函数。本文将深入探讨Protocol调用的进阶技巧,包括多实例Protocol的处理、Protocol的打开与关闭机制,以及错误处理的最佳实践。

一、多实例Protocol的定位与选择

在实际的UEFI系统中,很多Protocol可能存在多个实例,例如系统中可能有多个块设备(硬盘、U盘等),每个设备都对应一个EFI_BLOCK_IO_PROTOCOL实例。此时,单纯使用LocateProtocol函数只能获取第一个匹配的实例,无法满足选择特定设备的需求。

针对这种情况,UEFI提供了LocateHandleBuffer和HandleProtocol组合使用的方法。首先通过LocateHandleBuffer函数获取所有支持指定Protocol的设备句柄,然后遍历这些句柄,通过HandleProtocol函数获取每个设备对应的Protocol实例,最后根据设备的特性(如设备名称、介质类型等)选择需要的实例。

示例代码如下:

EFI_STATUS Status;
EFI_HANDLE *HandleBuffer;
UINTN HandleCount;
EFI_BLOCK_IO_PROTOCOL *BlockIo;
EFI_DEVICE_PATH_PROTOCOL *DevicePath;

// 获取所有支持BlockIo Protocol的设备句柄
Status = gBS->LocateHandleBuffer(ByProtocol, &gEfiBlockIoProtocolGuid, NULL, &HandleCount, &HandleBuffer);
if (EFI_ERROR(Status)) {
   return Status;
}

// 遍历设备句柄,选择第一个可移动设备
for (UINTN i = 0; i < HandleCount; i++) {
   Status = gBS->HandleProtocol(HandleBuffer[i], &gEfiBlockIoProtocolGuid, (VOID **)&BlockIo);
   if (EFI_ERROR(Status)) {
       continue;
   }

   // 检查设备是否为可移动介质
   if (BlockIo->Media->RemovableMedia) {
       // 获取设备路径并打印
       Status = gBS->HandleProtocol(HandleBuffer[i], &gEfiDevicePathProtocolGuid, (VOID **)&DevicePath);
       if (!EFI_ERROR(Status)) {
           Print(L"Found removable device: ");
           PrintDevicePath(DevicePath);
           Print(L"\n");
       }
       break;
   }
}

// 释放句柄缓冲区
gBS->FreePool(HandleBuffer);

二、Protocol的打开与关闭机制

在调用Protocol的服务函数之前,需要先打开Protocol。UEFI提供了OpenProtocol函数用于打开Protocol,使用完成后需要调用CloseProtocol函数关闭Protocol,以释放相关资源。

OpenProtocol函数的Attributes参数用于指定打开Protocol的方式,常用的属性包括:

  • EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL:通过句柄打开Protocol,适用于应用程序调用

  • EFI_OPEN_PROTOCOL_EXCLUSIVE:独占打开Protocol,防止其他模块同时访问

  • EFI_OPEN_PROTOCOL_TEST_PROTOCOL:测试Protocol是否存在,不实际打开

示例代码如下:

EFI_STATUS Status;
EFI_BLOCK_IO_PROTOCOL *BlockIo;
EFI_HANDLE Handle; // 假设已经获取到设备句柄

// 打开BlockIo Protocol
Status = gBS->OpenProtocol(
   Handle,
   &gEfiBlockIoProtocolGuid,
   (VOID **)&BlockIo,
   gImageHandle,
   NULL,
   EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL
);
if (EFI_ERROR(Status)) {
   return Status;
}

// 调用Protocol服务函数
Status = BlockIo->ReadBlocks(BlockIo, BlockIo->Media->MediaId, 0, 512, Buffer);
if (EFI_ERROR(Status)) {
   // 关闭Protocol
   gBS->CloseProtocol(Handle, &gEfiBlockIoProtocolGuid, gImageHandle, NULL);
   return Status;
}

// 关闭Protocol
Status = gBS->CloseProtocol(Handle, &gEfiBlockIoProtocolGuid, gImageHandle, NULL);

三、错误处理的最佳实践

在调用Protocol的过程中,可能会遇到各种错误,如Protocol不存在、设备错误、参数错误等。正确的错误处理对于提高应用程序的稳定性和可靠性至关重要。

以下是一些错误处理的最佳实践:

  1. 检查所有函数的返回值:UEFI中的大多数函数都会返回EFI_STATUS类型的错误码,必须对每个函数的返回值进行检查。

  2. 使用EFI_ERROR宏判断错误:EFI_ERROR宏可以用来判断返回值是否为错误码,例如:

    if (EFI_ERROR(Status)) {
       Print(L"Error: %r\n", Status);
       return Status;
    }

  3. 提供详细的错误信息:在发生错误时,应该打印详细的错误信息,包括错误码和错误描述,方便调试和问题定位。

  4. 清理资源:在发生错误时,应该及时释放已经分配的资源,如内存、打开的Protocol等。

  5. 返回合适的错误码:应用程序应该根据实际情况返回合适的错误码,而不是简单地返回EFI_FAILURE。

四、Protocol的版本兼容性

不同版本的Protocol可能会有不同的接口定义,因此在调用Protocol之前,需要检查Protocol的版本是否兼容。Protocol的版本信息通常包含在Protocol结构体的Revision字段中。

示例代码如下:

EFI_STATUS Status;
EFI_BLOCK_IO_PROTOCOL *BlockIo;

// 假设已经获取到BlockIo Protocol实例

// 检查Protocol版本是否兼容
if (BlockIo->Revision < EFI_BLOCK_IO_PROTOCOL_REVISION2) {
   Print(L"BlockIo Protocol version is too old\n");
   return EFI_UNSUPPORTED;
}

// 调用新版本Protocol的服务函数
Status = BlockIo->ReadBlocksEx(BlockIo, BlockIo->Media->MediaId, 0, 512, Buffer);

通过本文的介绍,相信读者已经掌握了UEFI Application调用Protocol的进阶技巧。在实际开发中,需要根据具体的需求和场景选择合适的调用方法,并注意错误处理和版本兼容性问题。