UEFI中的Protocol基础结构及其在内核中的表示
前言
在编写UEFI程序的过程中我们会经常使用Protocol。Protocol类似于服务器与客户端的之间的一种约定,双方根据这种约定信息互通信息。这里的服务器和客户端是一种广义的称呼,提供服务的叫服务器,使用服务的成为客户端。比如常见的TCP、COM等都是Protocol。在使用Protocol之前,还是要仔细了解一下,它到底是什么样子的。
一、Protocol的结构
Protocol引入了面向对象的思想来设计管理。它用struct
来模拟class
;用函数指针(Protolcol的成员变量)模拟成员函数,此函数的第一参数必须指向Protocol的指针,用来模拟this指针。
/// /EDK2/MdePkg/Include/Protocol/BlockIo.h
struct _EFI_BLOCK_IO_PROTOCOL {
///
/// The revision to which the block IO interface adheres. All future
/// revisions must be backwards compatible. If a future version is not
/// back wards compatible, it is not the same GUID.
///
UINT64 Revision;
///
/// Pointer to the EFI_BLOCK_IO_MEDIA data for this device.
///
EFI_BLOCK_IO_MEDIA *Media;
EFI_BLOCK_RESET Reset;
EFI_BLOCK_READ ReadBlocks;
EFI_BLOCK_WRITE WriteBlocks;
EFI_BLOCK_FLUSH FlushBlocks;
};
注意,Protocol的版本号必须向后兼容,如果不能兼容就需要对外来版本定义不同的GUID。每个Protocol必须有一个为一的GUID。如下:
/// /EDK2/MdePkg/Include/Protocol/BlockIo.h
#define EFI_BLOCK_IO_PROTOCOL_GUID \
{ \
0x964e5b21, 0x6459, 0x11d2, {0x8e, 0x39, 0x0, 0xa0, 0xc9, 0x69, 0x72, 0x3b } \
}
typedef struct _EFI_BLOCK_IO_PROTOCOL EFI_BLOCK_IO_PROTOCOL;
结构体EFI_BLOCK_IO_PROTOCOL
有两个成员变量和四个成员函数(其实本质是成员变量,这个变量是函数指针)。
下面是 EFI_BLOCK_IO_PROTOCOL
的ReadBlocks服务的函数原型:
/**
Read BufferSize bytes from Lba into Buffer.
@param This Indicates a pointer to the calling context.
@param MediaId Id of the media, changes every time the media is replaced.
@param Lba The starting Logical Block Address to read from
@param BufferSize Size of Buffer, must be a multiple of device block size.
@param Buffer A pointer to the destination buffer for the data. The caller is
responsible for either having implicit or explicit ownership of the buffer.
@retval EFI_SUCCESS The data was read correctly from the device.
@retval EFI_DEVICE_ERROR The device reported an error while performing the read.
@retval EFI_NO_MEDIA There is no media in the device.
@retval EFI_MEDIA_CHANGED The MediaId does not matched the current device.
@retval EFI_BAD_BUFFER_SIZE The Buffer was not a multiple of the block size of the device.
@retval EFI_INVALID_PARAMETER The read request contains LBAs that are not valid,
or the buffer is not on proper alignment.
**/
typedef
EFI_STATUS
(EFIAPI *EFI_BLOCK_READ)(
IN EFI_BLOCK_IO_PROTOCOL *This,
IN UINT32 MediaId,
IN EFI_LBA Lba,
IN UINTN BufferSize,
OUT VOID *Buffer
);
它的第一个参数 *This
指向的是EFI_BLOCK_TOPROTOCOL对象自己的This指针,就是指向它的一个实例。这方面它已经具备了class的一些特征了。与C++不同的是成员函数This指针是自动加入,而Protocol成员函数
的This指针需要手动加入。
二、Protocol在UEFI内核中的表示
在使用Protocol之前,需要先了解Protocol在UEFI内核中的表示。
EFI_HANDLE
typedef VOID *EFI_HANDLE;
EFI_HANDLE是指向某种对象的指针,UEFI用它来表示某个对象。UEFI扫描总线后,会为每个设备建立一个 Controller,用于控制设备,所有设备的驱动以Protocol的形式安装到Controller,这个Controller就是EFI_HANDLE对象。当我们将一个.efi文件加载到内存中时,UEFI会为该文件建立一个Image对象,这个Image对象也是一个EFI_HANDLE对象。在UEFI内部,EFI_HANDLE被理解为IHANDLE,IHANDLE的数据结构如下:
///IHANDLE - 包含了 Protocols 链表
typedef struct{
UINTN Signature;
LIST_ENTRY AllHandles;
LIST_ENTRY Protocols;
UINTN LocateRequest;
UINT64 Key;
}IHANDLE
每个IHANDLE中都有一个Protocol链表,存放属于自己的Protocol。所有的IAHDNLE通过AllHandles链接起来。IHANDLE的Protocols是一个双向链表,链表中的每一个元素是PROTOCOL_INTERFACE,通过PROTOCOL_INTERFACE的Protocol指针可以得到这个Protocol的GUID,通过interface指针就可以找到这个Protocol的实例。
注:图片来源与《UEFI原理与编程》
三、总结
这部分简单介绍了Protocol的结构和在UEFI内核中的表示,算是使用protocol之前对它简单的认识。后面将会讲到,Protocol如何使用。
参考资料
- 《UEFI原理与编程》
- UEFI Spec 2_6
- EDK2 GitHub