什么是protocol從字面意思上看,protocol是server和client之間的一種約定,雙方根據(jù)這種約定互通信息。這里的server和client是一種廣義的稱呼,提供服務(wù)的稱為server,使用服務(wù)的稱為client。 TCP是一種protocol, client(應(yīng)用程序)通過一組函數(shù)來壓包和解包,壓包和解包是server提供的服務(wù)。COM也是一種protocol,client通過
CoCreateInstance(...)和GUID獲得指向COM對象的指針,然后使用該指針獲得COM對象提供的服務(wù), GUID標(biāo)示了這個(gè)COM對象。現(xiàn)在我們對protocol有了概念上的理解,那么具體到UEFI里,protocol是什么樣子呢? 如何標(biāo)示一個(gè)protocol?如何得到protocol對應(yīng)的對象?...容我慢慢道來.
在講protocol什么樣子之前,還要插幾句C與C++的區(qū)別。我們知道UEFI是用C來開發(fā)的,C是面向過程的一種語言。而管理和使用UEFI眾多的protocol完全使用面向過程的思想會(huì)使程序變得復(fù)雜。protocol作為一種對象來設(shè)計(jì)管理會(huì)比較直觀。因而UEFI中的Protocol引入了面向?qū)ο蟮乃枷?,用struct來模擬class, Protocol用struct來實(shí)現(xiàn),用函數(shù)指針(Protocol的成員變量)模擬成員函數(shù),此種函數(shù)的第一參數(shù)必須是指向Protocol的指針,用來模擬this指針。
Protocol的摸樣
以EFI_DISKIO_PROTOCOL 來看看Protocol的樣子。 MdePkg/Include/Protocol/BlockIo.h
:220
///
/// This protocol provides control over block devices.
///
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;
};
extern EFI_GUID gEfiBlockIoProtocolGuid;
MdePkg/Include/Protocol/BlockIo.h
:220#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 有兩個(gè)成員變量,四個(gè)成員函數(shù)(當(dāng)然從C的角度來看,“成員函數(shù)”叫法不準(zhǔn)確,它實(shí)際上也是一個(gè)成員變量,只是這個(gè)變量是函數(shù)指針). gEfiBlockIoProtocolGuid({0x964e5b21, 0x6459, 0x11d2, {0x8e, 0x39, 0x0, 0xa0, 0xc9, 0x69, 0x72, 0x3b })標(biāo)示了EFI_BLOCK_IO_PROTOCOL 。
來看成員函數(shù)的聲明
/**
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
);
EFI_BLOCK_READ具體用法我們先不看,我們來看它的第一個(gè)參數(shù),指向
EFI_BLOCK_IO_PROTOCOL 對象自己的this指針,這是成員函數(shù)區(qū)別于一般函數(shù)的重要特征。
如何使用Protocol 使用Protocol之前,我們要弄清楚Protocol位于什么地方。首先我們要來認(rèn)識(shí)一下EFI_HANDLE,
///
/// A collection of related interfaces.
///
typedef VOID *EFI_HANDLE;
EFI_HANDLE是指向某種對象的指針,UEFI用它來表示某個(gè)對象。 UEFI掃描總線后,會(huì)為每個(gè)設(shè)備建立一個(gè)Controller對象,用于控制設(shè)備,所有該設(shè)備的驅(qū)動(dòng)以protocol的形式安裝到這個(gè)controller中,這個(gè)Controller就是一個(gè)EFI_HANDLE對象。 當(dāng)我們將一個(gè).efi文件加載到內(nèi)存中,UEFI也會(huì)為該文件建立一個(gè)Image對象(此Image非圖像的意識(shí)), 這個(gè)Image對象也是一個(gè)EFI_HANDLE對象。 在UEFI內(nèi)部,EFI_HANDLE被理解為IHANDLE
///
/// IHANDLE - contains a list of protocol handles
///
typedef struct {
UINTN Signature;
/// All handles list of IHANDLE
LIST_ENTRY AllHandles;
/// List of PROTOCOL_INTERFACE's for this handle
LIST_ENTRY Protocols;
UINTN LocateRequest;
/// The Handle Database Key value when this handle was last created or modified
UINT64 Key;
} IHANDLE;
每個(gè)IHANDLE中都有一個(gè)Protocols鏈表,存放屬于自己的protocol。所有的IHANDLE通過AllHandles鏈接起來。
要使用Protocol,首先要找到protocol對象,可以通過BootServices的OpenProtocol(...), HandleProtocl(...), LocateProtocol(...)獲得。
typedef
/**
Queries a handle to determine if it supports a specified protocol. If the protocol is supported by the
handle, it opens the protocol on behalf of the calling agent.
@param Handle The handle for the protocol interface that is being opened.
@param Protocol The published unique identifier of the protocol.
@param Interface Supplies the address where a pointer to the corresponding Protocol
Interface is returned.
@param AgentHandle The handle of the agent that is opening the protocol interface
specified by Protocol and Interface.
@param ControllerHandle If the agent that is opening a protocol is a driver that follows the
UEFI Driver Model, then this parameter is the controller handle
that requires the protocol interface. If the agent does not follow
the UEFI Driver Model, then this parameter is optional and may
be NULL.
@param Attributes The open mode of the protocol interface specified by Handle
and Protocol.
@retval EFI_SUCCESS An item was added to the open list for the protocol interface, and the
protocol interface was returned in Interface.
@retval EFI_UNSUPPORTED Handle does not support Protocol.
@retval EFI_INVALID_PARAMETER One or more parameters are invalid.
@retval EFI_ACCESS_DENIED Required attributes can't be supported in current environment.
@retval EFI_ALREADY_STARTED Item on the open list already has requierd attributes whose agent
handle is the same as AgentHandle.
**/
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的提供者,如果Handle的Protocols鏈表中有該P(yáng)otocol,Protocol對象的指針寫到*Interface
,并返回EFI_SUCCESS;否則 返回
EFI_UNSUPPORTED 。
如果在驅(qū)動(dòng)中調(diào)用OpenProtocol(), AgentHandle是擁有該
EFI_DRIVER_BINDING_PROTOCOL對象的Handle;ControllerHandle是擁有該驅(qū)動(dòng)的Controller。
如果調(diào)用OpenProtocol的是應(yīng)用程序,那么AgentHandle是該應(yīng)用對應(yīng)的Handle,也就main函數(shù)的第一個(gè)參數(shù)。 ControllerHandle此時(shí)可以忽略。
Attributes可以取以下5種值。
#define EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL 0x00000001
#define EFI_OPEN_PROTOCOL_GET_PROTOCOL 0x00000002
#define EFI_OPEN_PROTOCOL_TEST_PROTOCOL 0x00000004
#define EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER 0x00000008
#define EFI_OPEN_PROTOCOL_BY_DRIVER 0x00000010
#define EFI_OPEN_PROTOCOL_EXCLUSIVE 0x00000020
HandleProtocol是OpenProtocol的簡化版,因?yàn)榇蟛糠智闆r下我們都不需要關(guān)心AgentHandle,
ControllerHandle和Attributes。
EFI_STATUS
EFIAPI
CoreHandleProtocol (
IN EFI_HANDLE UserHandle,
IN EFI_GUID *Protocol,
OUT VOID **Interface
)
{
return CoreOpenProtocol (
UserHandle,
Protocol,
Interface,
gDxeCoreImageHandle,
NULL,
EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL
);
}
LocateProtocol(...)是從內(nèi)核中找出指定Protocol的第一個(gè)實(shí)例。
typedef
EFI_STATUS
LocateProtocol (
IN EFI_GUID *Protocol,
IN VOID *Registration OPTIONAL,
OUT VOID **Interface
);
UEFI內(nèi)核中某個(gè)Protocol的實(shí)例可能不止一個(gè),例如每個(gè)硬盤及每個(gè)分區(qū)都有一個(gè)EFI_DISK_IO_PROTOCOL實(shí)例。LocateProtocol順序搜索HANDLE鏈表,返回找到的第一個(gè)該P(yáng)rotocol的實(shí)例。
我們可以用BootServices提供的其它函數(shù)處理HANDLE和Protocol。
typedef
EFI_STATUS
LocateHandleBuffer (
IN EFI_LOCATE_SEARCH_TYPE SearchType,
IN EFI_GUID *Protocol OPTIONAL,
IN VOID *SearchKey OPTIONAL,
IN OUT UINTN *NoHandles,
OUT EFI_HANDLE **Buffer
); 可以獲得所有支持指定Protocol的HANDLE,
SearchType 有三種:AllHandles(查找所有HANDLE), ByRegisterNotify, ByProtocol(查找支持指定Protocol的HANDLE)。NoHandles是找到的HANDLE的數(shù)量, Buffer數(shù)組由UEFI復(fù)雜分配,由用戶負(fù)責(zé)釋放。
typedef
EFI_STATUS
LocateHandle (
IN EFI_LOCATE_SEARCH_TYPE SearchType,
IN EFI_GUID *Protocol OPTIONAL,
IN VOID *SearchKey OPTIONAL,
IN OUT UINTN *BufferSize,
OUT EFI_HANDLE *Buffer
);
與LocateHandleBuffer相似,只是用戶負(fù)責(zé)分配和釋放Buffer數(shù)組。
typedef
EFI_STATUS
ProtocolsPerHandle (
IN EFI_HANDLE Handle,
OUT EFI_GUID ***ProtocolBuffer,
OUT UINTN *ProtocolBufferCount
);
獲得指定Handle所支持的所有Protocol, UEFI負(fù)責(zé)分配內(nèi)存給
ProtocolBuffer,用戶負(fù)責(zé)釋放該內(nèi)存。
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
);
typedef struct {
EFI_HANDLE AgentHandle;
EFI_HANDLE ControllerHandle;
UINT32 Attributes;
UINT32 OpenCount;
} EFI_OPEN_PROTOCOL_INFORMATION_ENTRY;
OpenProtocolInformation()獲得指定Handle中指定Protocol的打開信息。
SPEC2.3.1第165頁有很好的例子演示了怎么打開一個(gè)Protocol,
EFI_BOOT_SERVICES *gBS;
EFI_HANDLE ImageHandle;
EFI_DRIVER_BINDING_PROTOCOL *This;
IN EFI_HANDLE ControllerHandle,
extern EFI_GUID gEfiXyzIoProtocol;
EFI_XYZ_IO_PROTOCOL *XyzIo;
EFI_STATUS Status;
Status = gBS->OpenProtocol (
ControllerHandle,
&gEfiXyzIoProtocol,
&XyzIo,
ImageHandle,
NULL,
EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL
);
Status = gBS->OpenProtocol (
ControllerHandle,
&gEfiXyzIoProtocol,
&XyzIo,
This->DriverBindingHandle,
ControllerHandle,
EFI_OPEN_PROTOCOL_GET_PROTOCOL
);
打開Protocol之后就可以使用了,最后要通過CloseProtocol關(guān)閉打開的Protocol。
typedef
EFI_STATUS
(EFIAPI *EFI_CLOSE_PROTOCOL) (
IN EFI_HANDLE Handle,
IN EFI_GUID *Protocol,
IN EFI_HANDLE AgentHandle,
IN EFI_HANDLE ControllerHandle
);
通過HandleProtocol和LocateProtocol打開的Protocol因?yàn)闆]有指定AgentHandle,所以無法關(guān)閉。如果一定要去關(guān)閉它,要調(diào)用OpenProtocolInformation()獲得AgentHandle和ControllerHandle,然后關(guān)閉它。
下面看一個(gè)完整的例子,用EFI_DISK_IO_PROTOCOL讀取GPT硬盤的分區(qū)表
#include <Uefi.h>
#include <Base.h>
#include <Library/UefiLib.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/PrintLib.h>
#include <Protocol/DiskIo.h>
#include <Protocol/BlockIo.h>
#include <Protocol/DevicePath.h>
#include <Uefi/UefiGpt.h>
#include <Library/DevicePathLib.h>
EFI_STATUSEFIAPIUefiMain( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ){ EFI_STATUS Status; UINTN HandleIndex, HandleCount; EFI_HANDLE *DiskControllerHandles = NULL; EFI_DISK_IO_PROTOCOL *DiskIo; /*找到所有提供
EFI_DISK_IO_PROTOCOL 的Controller */ Status = gBS->LocateHandleBuffer( ByProtocol, &gEfiDiskIoProtocolGuid, NULL, &HandleCount, &DiskControllerHandles); if (!EFI_ERROR(Status)) { CHAR8 gptHeaderBuf[512];
EFI_PARTITION_TABLE_HEADER* gptHeader = (EFI_PARTITION_TABLE_HEADER*
)gpHeaderBuf;
for (HandleIndex = 0; HandleIndex < HandleCount; HandleIndex++) { /*打開EFI_DISK_IO_PROTOCOL */ Status = gBS->HandleProtocol( DiskControllerHandles[HandleIndex], &gEfiDiskIoProtocolGuid, (VOID**)&DiskIo); if (!EFI_ERROR(Status)){ { EFI_DEVICE_PATH_PROTOCOL *DiskDevicePath; EFI_DEVICE_PATH_TO_TEXT_PROTOCOL *Device2TextProtocol = 0; CHAR16* TextDevicePath = 0; /*1. 打開EFI_DEVICE_PATH_PROTOCOL */ Status = gBS->OpenProtocol( DiskControllerHandles[HandleIndex], &gEfiDevicePathProtocolGuid, (VOID**)&DiskDevicePath, ImageHandle, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL ); if(!EFI_ERROR(Status)){ if(Device2TextProtocol == 0) Status = gBS->LocateProtocol( &gEfiDevicePathToTextProtocolGuid, NULL, (VOID**)&Device2TextProtocol ); /*2. 使用
EFI_DEVICE_PATH_PROTOCOL 得到文本格式的Device Path */ TextDevicePath = Device2TextProtocol->ConvertDevicePathToText(DiskDevicePath, TRUE, TRUE); Print(L"%s\n", TextDevicePath); if(TextDevicePath)gBS->FreePool(TextDevicePath); /*3. 關(guān)閉 EFI_DEVICE_PATH_PROTOCO */ Status = gBS->CloseProtocol( DiskControllerHandles[HandleIndex], &gEfiDevicePathProtocolGuid, ImageHandle, ); } } { EFI_BLOCK_IO_PROTOCOL* BlockIo = *(EFI_BLOCK_IO_PROTOCOL**) (DiskIo + 1); EFI_BLOCK_IO_MEDIA* Media = BlockIo->Media; /*讀1號(hào)扇區(qū)。 */ Status = DiskIo->ReadDisk(DiskIo, Media->MediaId, 512, 512, gptHeader); /*檢查GPT標(biāo)志。 */ if((!EFI_ERROR(Status)) &&( gptHeader -> Header.Signature == 0x5452415020494645)){ UINT32 CRCsum; UINT32 GPTHeaderCRCsum = (gptHeader->Header.CRC32); gptHeader->Header.CRC32 = 0; gBS -> CalculateCrc32(gptHeader , (gptHeader->Header.HeaderSize), &CRCsum); if(GPTHeaderCRCsum == CRCsum){ // Find out a GPT Header
} } } } } gBS->FreePool(DiskControllerHandles); }}