出處:http://blog.csdn.net/celiaqianhj/article/details/6764433
一、一些概念的理解
EFI_HANDLE定义是这样的:typedef void * EFI_HANDLE。void * 用C语言来理解为不确定类型。它真正的类型是这样定义的(EDKFoundationCoreDxeHandHand.h):
EFI_HANDLE Handle; // Back pointer
EFI_LIST_ENTRY Link; // Link on IHANDLE.Protocols
EFI_LIST_ENTRY ByProtocol; // Link on PROTOCOL_ENTRY.Protocols
PROTOCOL_ENTRY *Protocol; // The protocol ID
VOID * Interface; // The interface value
EFI_LIST_ENTRY OpenList; // OPEN_PROTOCOL_DATA list.
UINTN OpenListCount; EFI_HANDLE ControllerHandle;
UINTN Signature;
EFI_LIST_ENTRY Link;
EFI_HANDLE AgentHandle;
EFI_HANDLE ControllerHandle;
UINT32 Attributes;
UINT32 OpenCount;
PROTOCOL_ENTRY的定义如下:
EFI Protocol 觀念小筆記
出處:http://biosengineer.blogspot.tw/2007/05/efi-protocol.html
C語言的宣告與定義:
1.C語言中只允許變數或函數的定義出現一次。
由於變數只有定義規則,沒有宣告規則,所以當你鍵入int a;時,代表此變數已經被定義了。我們如何宣告一個變數呢? 加上extern 變成exern int a;,告訴comipler,此變數a只是一個宣告,它的定義在別處。
2. C語言函數規則分成宣告與定義二種。當一個函數只有傳回值、名稱、傳入值, 沒有大括號{},compiler會把此種形式當成是函數宣告;如果加上了{}, 形成函數定義。所以你要宣告一個函數,只須鍵入int f(); compiler會自動把它示為函數宣告。
// EFI Protocol
——————
EFI Protocol 可以看作是一個介面(Interface),透過這個Interface 去存取Data 或是函數,簡單說就是一個指標,指標指向的記憶體可能是Data,也可能是函數。
安裝一個 EFI Protocol 後會以 (GUID | Pointer) 的欄位格式存放在Handle DataBase中,然後就可以在其他地方利用LocateProtcol()方式去得到指標,然後使用他。
1.Data 型式的EFI Protocol
(a)宣告你的Sturcture與GUID在你的Protocol.h 與Protocol.c中
EFI_GUID gGuid=MY_GUID;
Extern gGuid;
typedef struct {
…
UINT8 Array[10];
…
} MY_PROTOCOL;
(b)在DXE Driver中使用使用MY_PROTOCOL “定義”一個變數Test
UINT16 i;
MY_PROTOCOL Test={……};
然後在呼叫InstallProtocol(&gGuid,&Test)去安裝一個Protocol到Handle Database,所以Handle Database中的Guid=gGuid,而指標Pointer=&Test。
2.Function 型式的EFI Protocol也是跟Data型式一樣作法,不同的地方在於宣告的是函數原型。
所以觀念在於如何去宣告Protocol與如何透過GUID+Pointer去做你要做的事情。
=================================================================================
淺談EFI Protocol
出處:http://iorlvskyo.blogspot.tw/2010/09/efi-protocol.html
UEFI 实战(4) protocol
出處:http://www.cppblog.com/djxzh/archive/2012/03/06/167106.html
什么是protocol
从字面意思上看,protocol是server和client之间的一种约定,双方根据这种约定互通信息。这里的server和client是一种广义的称呼,提供服务的称为server,使用服务的称为client。 TCP是一种protocol, client(应用程序)通过一组函数来压包和解包,压包和解包是server提供的服务。COM也是一种protocol,client通过CoCreateInstance(…)和GUID获得指向COM对象的指针,然后使用该指针获得COM对象提供的服务, GUID标示了这个COM对象。现在我们对protocol有了概念上的理解,那么具体到UEFI里,protocol是什么样子呢? 如何标示一个protocol?如何得到protocol对应的对象?…容我慢慢道来.
在讲protocol什么样子之前,还要插几句C与C++的区别。我们知道UEFI是用C来开发的,C是面向过程的一种语言。而管理和使用UEFI众多的protocol完全使用面向过程的思想会使程序变得复杂。protocol作为一种对象来设计管理会比较直观。因而UEFI中的Protocol引入了面向对象的思想,用struct来模拟class, Protocol用struct来实现,用函数指针(Protocol的成员变量)模拟成员函数,此种函数的第一参数必须是指向Protocol的指针,用来模拟this指针。
Protocol的摸样
以EFI_DISKIO_PROTOCOL 来看看Protocol的样子。
: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;
:220
{
0x964e5b21, 0x6459, 0x11d2, {0x8e, 0x39, 0x0, 0xa0, 0xc9, 0x69, 0x72, 0x3b }
}
typedef struct _EFI_BLOCK_IO_PROTOCOL EFI_BLOCK_IO_PROTOCOL;
来看成员函数的声明
/**
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具体用法我们先不看,我们来看它的第一个参数,指向EFI_BLOCK_IO_PROTOCOL 对象自己的this指针,这是成员函数区别于一般函数的重要特征。
如何使用Protocol
使用Protocol之前,我们要弄清楚Protocol位于什么地方。首先我们要来认识一下EFI_HANDLE,
/// A collection of related interfaces.///typedef VOID *EFI_HANDLE;
EFI_HANDLE是指向某种对象的指针,UEFI用它来表示某个对象。 UEFI扫描总线后,会为每个设备建立一个Controller对象,用于控制设备,所有该设备的驱动以protocol的形式安装到这个controller中,这个Controller就是一个EFI_HANDLE对象。 当我们将一个.efi文件加载到内存中,UEFI也会为该文件建立一个Image对象(此Image非图像的意识), 这个Image对象也是一个EFI_HANDLE对象。 在UEFI内部,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;
每个IHANDLE中都有一个Protocols链表,存放属于自己的protocol。所有的IHANDLE通过AllHandles链接起来。
要使用Protocol,首先要找到protocol对象,可以通过BootServices的OpenProtocol(…), HandleProtocl(…), LocateProtocol(…)获得。
EFI_STATUS
Handle是Protocol的提供者,如果Handle的Protocols链表中有该Potocol,Protocol对象的指针写到*Interface,并返回EFI_SUCCESS;否则 返回EFI_UNSUPPORTED 。
如果在驱动中调用OpenProtocol(), AgentHandle是拥有该EFI_DRIVER_BINDING_PROTOCOL对象的Handle;ControllerHandle是拥有该驱动的Controller。
如果调用OpenProtocol的是应用程序,那么AgentHandle是该应用对应的Handle,也就main函数的第一个参数。 ControllerHandle此时可以忽略。
#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的简化版,因为大部分情况下我们都不需要关心AgentHandle,ControllerHandle和Attributes。
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(…)是从内核中找出指定Protocol的第一个实例。
EFI_STATUS
LocateProtocol (
IN EFI_GUID *Protocol,
IN VOID *Registration OPTIONAL,
OUT VOID **Interface
);
UEFI内核中某个Protocol的实例可能不止一个,例如每个硬盘及每个分区都有一个EFI_DISK_IO_PROTOCOL实例。LocateProtocol顺序搜索HANDLE链表,返回找到的第一个该Protocol的实例。
我们可以用BootServices提供的其它函数处理HANDLE和Protocol。
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的数量, Buffer数组由UEFI复杂分配,由用户负责释放。
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相似,只是用户负责分配和释放Buffer数组。
EFI_STATUS
ProtocolsPerHandle (
IN EFI_HANDLE Handle,
OUT EFI_GUID ***ProtocolBuffer,
OUT UINTN *ProtocolBufferCount
);
获得指定Handle所支持的所有Protocol, UEFI负责分配内存给ProtocolBuffer,用户负责释放该内存。
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
);
OpenProtocolInformation()获得指定Handle中指定Protocol的打开信息。
SPEC2.3.1第165页有很好的例子演示了怎么打开一个Protocol,
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关闭打开的Protocol。
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因为没有指定AgentHandle,所以无法关闭。如果一定要去关闭它,要调用OpenProtocolInformation()获得AgentHandle和ControllerHandle,然后关闭它。
下面看一个完整的例子,用EFI_DISK_IO_PROTOCOL读取GPT硬盘的分区表
EFI_STATUS
EFIAPI
UefiMain(
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”%sn”, TextDevicePath);
if(TextDevicePath)gBS->FreePool(TextDevicePath);
/*3. 关闭 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号扇区。 */
Status = DiskIo->ReadDisk(DiskIo, Media->MediaId, 512, 512, gptHeader);
/*检查GPT标志。 */
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);
}
}