UEFI中的Protocol使用方法

UEFI中的Protocol使用方法

前言

  启动服务提供了丰富的服务供开发者操作Protocol,我们可以使用Protocol也可以开发Protocol。本文主要介绍如何使用Protocol。
  使用Protocol一般分为下面三个步骤:
    1. 通过启动服务找出Protocol对象;
    2. 使用这个Protocol提供的服务;
    3. 关闭打开的Protocol
  可以使用OpenProtocolInformation服务查看打开某个Protocol的所有设备。
  启动服务,有四种,分别为:OpenProtocol、HandleProtocol、LocateProtocol和LocalHandleBuffer。下面分别介绍它们。

一、OpenProtocol 服务

  OpenProtocol 用于查询指定的Handle中是否支持指定的Protocol,如果支持,则打开该Protocol,否则返回错误代码。
  BS(启动时服务)中的OpenProtocol函数原型:

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
);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

返回值:

返回值 描述
EFI_SUCCESS The interface information for the specified protocol was returned.
EFI_UNSUPPORTED The device does not support the specified protocol.
EFI_INVALID_PARAMETER Handle is NULL.
EFI_INVALID_PARAMETER Protocol is NULL.
EFI_INVALID_PARAMETER Interface is NULL.
  • Handle:查询次Handle提供的Protocol
  • *Protocol:要打开的Protocol(指向次Protocol GUID的指针)
  • **Interface:返回打开的Protocol的对象
  • AgentHandle:打开此Protocol的Image
  • ControllerHandle:打开此Protocol的控制器
  • Attributes:打开此Protocol的方式

      Handle是Protocol的提供者,如果Handle的Protocols链表中有该Protocol,则Protocol对象的指针写到*Interface,并返回EFI_SUCCESS,否则返回EFI_UNSUPPORTED。
      如果在驱动中调用OpenProtocol,则ControllerHandle是拥有该驱动的控制器,也就是请求使用这个Protocol的控制器;AgentHandle是拥有该EFI_DRIVER_BINDING_PROTOCOL对象的Handle。EFI_DRIVER_BINDING_PROTOCOL是UEFI驱动开发一定会用到的一个Protocol,它负责驱动的安装与卸载。
      如果调用OpenProtocol的是应用程序,那么AgentHandle是该用用程序的Handle,也就是UefiMain的第一个参数,ControllerHandle此时可以忽略。
      Attribute值列表:
     这里写图片描述
    注:截图来源于UEFI Spec 2_6。关于这部分可以查阅UEFI Spec。

OpenHandle示例程序片断:

/// 打开BlockIo
EFI_HANDLE ImageHandle;
EFI_DRIVER_BINDING_PROTOCOL *This;
IN EFI_HADNLE ControllerHandle;
extern EFI_GUID BlockIoProtocolGuid;
EFI_BLOCK_IO_PROTOCOL *BlockIo;
EFI_STATUS Status;
//在应用程序中使用OpenProtocol,一般ControllerHandle会设为NULL
Status = gBS->OpenProtocol(
    ControllerHandle,                     //Handle
    &gEfiBlockIoProtocolGuid,             //Protocol
    &BlockIo,                             //Interface
    ImageHandle,                          //AgentHandle
    NULL,                                 //ControllerHandle
    EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL  //Attribute
)
//在驱动中使用,OpenProtocol, ControllerHandle是控制器的Handle
Status = gBS->OpenProtocol(
    ControllerHandle,                     //Handle
    &gEfiBlockIoProtocolGuid,             //Protocol
    &BlockIo,                             //Interface
    This->DriverBindingHandle,            //AgentHandle
    ControllerHandle,                     //ControllerHandle
    EFI_OPEN_PROTOCOL_GET_PROTOCOL        //Attribute
)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

gEfiBlockIoProtocolGuid 是全局变量,变量定义在AutoGen.c 中。AutoGen.c 是由build工具根据 .inf 和 .dec 文件生成的,gEfiBlockIoProtocolGuid是EFI_GUID类型的变量,其EFI_GUID值定义在 .dec中。如果要在应用程序或驱动中使用gEfiBlockIoProtocolGuid,那么必须在 .inf 文件的[Protocols]中声明以便预处理时将其包含在AutoGen.c中。

二、HandleProtocol 服务

  OpenProtocol功能比较强大,但是使用比较复杂,需要提供Handle和Protocol的GUID,还要提供AgentHandle、ControllerHandle和Attributes。但是开发者大多时候只是想通过Protocol的GUID得到Protocol对象,并不关心打开方式细节。为了方便开发者使用Protocol,启动服务提供了HandleProtocol以简化打开Protocol。
  HandleProtocol服务的函数原型:

typedef
EFI_STATUS
(EFIAPI *EFI_HANDLE_PROTOCOL) (
    IN EFI_HANDLE Handle,
    IN EFI_GUID *Protocol,
    OUT VOID **Interface
);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
返回值 描述
EFI_SUCCESS The interface information for the specified protocol was returned.
EFI_UNSUPPORTED The device does not support the specified protocol.
EFI_INVALID_PARAMETER Handle is NULL.
EFI_INVALID_PARAMETER Protocol is NULL.
EFI_INVALID_PARAMETER Interface is NULL.

HandleProtocol 是OpenProtocol的简化版,在调用HandleProtocol时不必再传入AgentHandle、ControllerHandle和Attribute。它的内部其实仍是调用了OpenProtocol。
  HandleProtocol 的内部实现:

EFI_STATUS
HandleProtocol (
    IN EFI_HANDLE Handle,
    IN EFI_GUID *Protocol,
    OUT VOID **Interface
)
{
    return OpenProtocol (
        Handle,
        Protocol,
        Interface,
        EfiCoreImageHandle,
        NULL,
        EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL
    );
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

HandleProtocol使用示例:

Status = gBS->HandleProtocol(
    ControllerHandle,                     //Handle
    &gEfiBlockIoProtocolGuid,             //Protocol
    &BlockIo,                             //Interface   
)
  • 1
  • 2
  • 3
  • 4
  • 5

三、LocateProtocol 服务

  OpenProtocol和HandleProtocol用于打开指定设备上的某个Protocol,要使用这两个函数,首先要得到这个设备的句柄 。有时开发者并不关心Protocol在哪个设备上,尤其是系统仅有一个该Protocol的实例时。启动服务提供了LocateProtocol(…)服务,它可以从UEFI内核中找到指定Protocol的第一个实例。
  LocateProtocol服务函数原型:

typedef
EFI_STATUS
(EFIAPI *EFI_LOCATE_PROTOCOL) (
    IN EFI_GUID *Protocol,
    IN VOID *Registration,OPTIONAL
    OUT VOID **Interface
);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
返回值 描述
EFI_SUCCESS A protocol instance matching Protocol was found and returned in Interface.
EFI_INVALID_PARAMETER Interface is NULL.
EFI_NOT_FOUND No protocol instances were found that match Protocol and Registration.

  UEFI内核中某个Protocol的实例可能不止一个,例如,每个硬盘及每个分区都有一个EFI_DISK_IO_PROTOCOL实例。LocateProtocol顺序搜索HANDLE链表,返回找到的第一个该Protocol的实例。
  下面的例子用于找出系统中第一个SIMPLE_FILE_SYSTEM_PROTOCOL实例。

EFI_SIMPLE_FILE_SYSTE_PROTOCOL* SimpleFs;
gBS->LocateProtocol(
    gEfiSimpleFileSystemProtocolGuid,
    NULL,
    &SimpleFs
);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

四、LocateProtocolBuffer服务

  前面三种是从设备上找出Protocol的方法。有时候开发者需要找出支持某个Protocol的所有设备,例如找出系统中所有安装了BlockIo的设备。LocateHandle和LocateProtocolBuffer提供这个服务。
  LocateProtocolBuffer函数原型:

typedef
EFI_STATUS
(EFIAPI *EFI_LOCATE_HANDLE_BUFFER) (
    IN EFI_LOCATE_SEARCH_TYPE SearchType, //查找方式
    IN EFI_GUID *Protocol OPTIONAL,       //指定的Protocol
    IN VOID *SearchKey OPTIONAL,          //PROTOCOL_NOTIFY的类型
    IN OUT UINTN *NoHandles,              //返回找到的HANDLE数量
    OUT EFI_HANDLE **Buffer               //分配Handle数组并返回
);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
返回值 描述
EFI_SUCCESS The array of handles was returned.
EFI_NOT_FOUND No handles match the search.
EFI_BUFFER_TOO_SMALL The BufferSize is too small for the result. BufferSize has been updated with the size needed to complete the request.
EFI_INVALID_PARAMETER SearchType is not a member of EFI_LOCATE_SEARCH_TYPE
EFI_INVALID_PARAMETER SearchType is ByRegisterNotify and SearchKey is NULL.
EFI_INVALID_PARAMETER SearchType is ByProtocol and Protocol is NULL.
EFI_INVALID_PARAMETER One or more matches are found and BufferSize is NULL.
EFI_INVALID_PARAMETER BufferSize is large enough for the result and Buffer is NULL.

  SearchType有三种:AllHandles用于找出系统中所有的Handle;ByRegisterNotify用于从RegisterProtocolNotify中找出所有匹配SearchKey的Handle;ByProtocol用于从系统Handle数据库中找到支持指定Protocol的HANDLE。
  SearchType的类型定义:

typedef enum{
    AllHandles,
    ByRegisterNotify,
    ByProtocol
} EFI_LOCATE_SEARCH_TYPE;
  • 1
  • 2
  • 3
  • 4
  • 5

LocateHandle服务函数原型:

typedef
EFI_STATUS
(EFIAPI *EFI_LOCATE_HANDLE) (
    IN EFI_LOCATE_SEARCH_TYPE SearchType,
    IN EFI_GUID *Protocol OPTIONAL,
    IN VOID *SearchKey OPTIONAL,
    IN OUT UINTN *BufferSize,
    OUT EFI_HANDLE *Buffer
);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
返回值 描述
EFI_SUCCESS The array of handles was returned.
EFI_NOT_FOUND No handles match the search.
EFI_BUFFER_TOO_SMALL The BufferSize is too small for the result. BufferSize has been updated with the size needed to complete the request.
EFI_INVALID_PARAMETER SearchType is not a member of EFI_LOCATE_SEARCH_TYPE
EFI_INVALID_PARAMETER SearchType is ByRegisterNotify and SearchKey is NULL.
EFI_INVALID_PARAMETER SearchType is ByProtocol and Protocol is NULL.
EFI_INVALID_PARAMETER One or more matches are found and BufferSize is NULL.
EFI_INVALID_PARAMETER BufferSize is large enough for the result and Buffer is NULL.

  LocateHandle服务与LocateHandleBuffer服务最大的不同是需要调用者负责管理Buffer数组占用的内存。

五、其它Protocol 服务

  除了Protocol和根据Protocol找出设备这些常用服务,启动服务关于使用Protocol的服务还有ProtocolsPerHandle和OpenProtocolInformation。

1. ProtocolsPerHandle

  ProtocolsPerHandle用于获取指定设备所支持的所有Protocol。这些Protocol的GUID通过ProtocolBuffer返回给调用者,UEFI负责分配内存给ProtocolBuffer,调动者负责释放该内存。
  
  ProtocolsPerHandle函数原型:

typedef
EFI_STATUS
(EFIAPI *EFI_PROTOCOLS_PER_HANDLE) (
    IN EFI_HANDLE Handle,
    OUT EFI_GUID ***ProtocolBuffer,
    OUT UINTN *ProtocolBufferCount
);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
返回值 描述
EFI_SUCCESS The list of protocol interface GUIDs installed on Handle was returned in ProtocolBuffer. The number of protocol interface GUIDs was returned in ProtocolBufferCount.
EFI_INVALID_PARAMETER Handle is NULL.
EFI_INVALID_PARAMETER ProtocolBuffer is NULL.
EFI_INVALID_PARAMETER ProtocolBufferCount is NULL.
EFI_OUT_OF_RESOURCES There is not enough pool memory to store the results

2. OpenProtocolInformation

  OpenProtocolInformation用于获得指定设备上指定Protocol的打开信息。

  OpenProtocolInformation函数原型:

typedef
EFI_STATUS
(EFIAPI *EFI_OPEN_PROTOCOL_INFORMATION) (
    IN EFI_HANDLE Handle,
    IN EFI_GUID *Protocol,
    OUT EFI_OPEN_PROTOCOL_INFORMATION_ENTRY **EntryBuffer,
    OUT UINTN *EntryCount
);  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
返回值 描述
EFI_SUCCESS The open protocol information was returned in EntryBuffer,and the number of entries was returned EntryCount.
EFI_NOT_FOUND Handle does not support the protocol specified by Protocol.
EFI_OUT_OF_RESOURCES There are not enough resources available to allocate EntryBuffer.
//EFI_OPEN_PROTOCOL_INFORMATION_ENTRY数据结构
typedef struct {
    EFI_HANDLE AgentHandle;
    EFI_HANDLE ControllerHandle;
    UINT32 Attributes;
    UINT32 OpenCount;
} EFI_OPEN_PROTOCOL_INFORMATION_ENTRY;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

  Handle是这个设备的句柄,打开的信息存放在PROTOCOL_INTERDACE的OpenList列表中。设备上的同一Protocol可能被打开和关闭很多次。Protocol每一次被成功打开和关闭后都会更新OpenList列表。成功打开后,都会在设备句柄上添加一项EFI_OPEN_PROTOCOL_INFORMATION_ENTRY,若OpenList列表中已经存在一项与当前的(AgentHandle,ControllerHandle,Attributes)完全相同,则该项OpenCount加一。关闭Protocol则将OpenList对应项的OpenCount减一,OpenCount为零时删除对应项。

六、CloseProtocol 服务

  Protocol使用完毕之后需要通过CloseProtocol关闭打开的Protocol。
  CloseProtocol函数原型:

typedef
EFI_STATUS
(EFIAPI *EFI_CLOSE_PROTOCOL) (
    IN EFI_HANDLE Handle,
    IN EFI_GUID *Protocol,
    IN EFI_HANDLE AgentHandle,
    IN EFI_HANDLE ControllerHandle
);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
返回值 描述
EFI_SUCCESS The protocol instance was closed.
EFI_INVALID_PARAMETER Handle is NULL.
EFI_INVALID_PARAMETER AgentHandle is NULL.
EFI_INVALID_PARAMETER ControllerHandle is not NULL and ControllerHandle is NULL.
EFI_INVALID_PARAMETER Protocol is NULL.
EFI_NOT_FOUND Handle does not support the protocol specified by Protocol.
EFI_NOT_FOUND The protocol interface specified by Handle and Protocol is not currently open by AgentHandle and ControllerHandle.

  通过HandleProtocol和LocateProtocol打开的Protocol因为没有指定AgentHandle,所以无法关闭。如果一定要关闭它,则要调用OpenProtocolInformation()获得AgentHandle和ControllerHandle,然后再关闭它。

七、总结

  本部分,不住要讲解了Protocol的使用方法。Protocol提供了一种在UEFI应用程序以及UEFI驱动之间的通信方式。通过Protocol,用户可以使用驱动提供的服务,以及系统提供的其他服务。要熟悉自己使用的Protocol的数据结构。

猜你喜欢

转载自blog.csdn.net/humanof/article/details/82866600