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不存在、设备错误、参数错误等。正确的错误处理对于提高应用程序的稳定性和可靠性至关重要。
以下是一些错误处理的最佳实践:
检查所有函数的返回值:UEFI中的大多数函数都会返回EFI_STATUS类型的错误码,必须对每个函数的返回值进行检查。
使用EFI_ERROR宏判断错误:EFI_ERROR宏可以用来判断返回值是否为错误码,例如:
if (EFI_ERROR(Status)) {
Print(L"Error: %r\n", Status);
return Status;
}提供详细的错误信息:在发生错误时,应该打印详细的错误信息,包括错误码和错误描述,方便调试和问题定位。
清理资源:在发生错误时,应该及时释放已经分配的资源,如内存、打开的Protocol等。
返回合适的错误码:应用程序应该根据实际情况返回合适的错误码,而不是简单地返回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的进阶技巧。在实际开发中,需要根据具体的需求和场景选择合适的调用方法,并注意错误处理和版本兼容性问题。
下一篇 >>