UEFI小结-Handle的来龙去脉



学习时间:2013/10/14

参考资料:http://www.biosren.com/thread-3440-1-1.html

说明:本文涉及的源代码具体见

附件 UDK2010.SR1.UP1.Complete-131011_compile-version.tar

 

本文说明:本人刚学习UEFI不久,写该文,一是为了将学到的东西做一个规范化的总结,二是为了给初学UEFI的人起到借鉴作用。要理解本文,您至少应该是读过UEFI Spec。

1 基本概念的理解

UEFI中会有很多抽象概念,像service、protocol、handle等等,如果将这些抽象的概念放到实际的代码中理解的话,会有更清晰地认识,有了清晰的认识之后再把它们作为抽象来理解,就遂心应手的多了。

1.1 protocol概念

protocol,其实它就是一个由struct定义的结构体,这个结构体通常是由数据和函数指针组成,或其一。每个结构体的定义都有一个GUID与之对应。自然并不是所有的结构体都称之为protocol,protocol正如其名,它是一种规范,或称协议。比如要建立一个基于UEFI Driver Model的Driver,就必须要绑定一个EFI_DRIVER_BINDING_PROTOCOL的实例,并且要自定义且实现Support、Start、Stop函数以及填充实例中其他的数据成员。它就相当于已经规范了种种需求和步骤



1.2 service概念

service它就是UEFI定义的API函数,所有的service都被集中到EFI_SYSTEM_TABLE下面(./MdePkg/Include/Uefi/Uefispec.h),都可以通过gST来调用(gST指向一个EFI_SYSTEM_TABLE的全局实例)。



1.3 Handle概念

EFI_HANDLE的定义

EFI_HANDLE定义是这样的:typedef void * EFI_HANDLE。void * 用C语言来理解为不确定类型。它真正的类型是这样定义的:

(./MdeModulePkg/Core/Dxe/Hand/Handle.h)

// 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;//注意此处是如何链接到PROTOCOL_INTERFACE结构体

UINTN LocateRequest;

// The Handle Database key value when this handle was last created or modified

UINT64 Key;

} IHANDLE;

比如定义一个变量EFI_HANDLE hExample,当你将它作为参数传递给service的时候,在service内部是这样使用它的:IHANDLE * Handle=(IHANDLE*)hExample。也就是说IHANDLE*才是handle的本来面目为什么要弄的这么复杂呢?一是为了抽象以隐藏细节,二可能是为了安全。

 

2 IHANDLE引出各种链表

(./MdeModulePkg/Core/Dxe/Hand/Handle.h)

// 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;//注意此处如何链接PROTOCOL_INTERFACE结构体

UINTN LocateRequest;

// The Handle Database key value when this handle was last created or modified

UINT64 Key;

} IHANDLE;

 

2.1 由IHANDLE中AllHandles引出的链表(struct LIST_ENTRY)

要明白IHANDLE这个结构体,就要明白LIST_ENTRY(/EFI_LIST_ENTRY兼容性)是如何被使用的。LIST_ENTRY定义如下

注意:LIST_ENTRY/EFI_LIST_ENTRY兼容性, _LIST_ENTRY定义在:

(EFI_LIST_ENTRY定义在

EdkCompatibilityPkg\Foundation\Library\Dxe\Include\LinkedList.h

 

./MdePkg/Include/Base.h

// LIST_ENTRY structure definition

typedef struct _LIST_ENTRY LIST_ENTRY;

(./MdePkg/Include/Base.h)

// _LIST_ENTRY structure definition

typedef struct _LIST_ENTRY {

struct LIST_ENTRY *ForwardLink;

struct LIST_ENTRY *BackLink;

};

大家立刻就会反应到,它用于实现双向链表。但是与一般的链表实现方式不一样,它纯粹是LIST_ENTRY这个成员的链接,而不在乎这个成员所在的结构体。一般的链表要求结点之间的类型一致,而这种链表只要求结构体存在LIST_ENTRY这个成员就够了。比如说IHANDLE *handle1,*handle2;

初始化:

handle1->AllHandles->ForwardLink=handle2->AllHandles; handle2->AllHandles->BackLink=handle1->AllHandles



 

图1

 

这样handle1与handle2的AllHandles就链接到了一起(如图1)。但是这样就只能进行AllHandles的遍历了,怎么样遍历IHANLE实例呢?。这时候就要用到_CR宏,_CR宏的定义如下:

(./BaseTools/Source/C/Include/Common/BaseTypes.h)

// _CR – return a pointer to the structure from one of it’s elements

#define _CR(Record, TYPE, Field) \

((TYPE *) ((CHAR8 *) (Record) - (CHAR8 *) &(((TYPE *) 0)->Field)))

这个宏可以通过结构体实例的成员访问到实例本身,它的原理可以参见

http://www.biosren.com/thread-1407-1-1.html 或者

http://blog.csdn.net/hgf1011/archive/2009/10/06/4635888.aspx

原理如下:

对于_CR这个macro,凡是搞UEFI的大家都会经常用到,其具体实现如下:

// CONTAINING_RECORD - returns a pointer to the structure

// from one of it's elements.

#define _CR(Record, TYPE, Field) \

((TYPE *) ((CHAR8 *) (Record) - (CHAR8 *) &(((TYPE *) 0)->Field)))

这个宏的作用是根据一个结构体成员变量的的地址获得该结构体基地址。

下面我们先来分析一个简单的例子:
typedef struct {

UINT16 a;

UINT16 b;

UINT32 c;

} PRIVATE_DATA;

知道成员变量b的地址,如何得到PRIVATE_DATA这个Structure的地址呢?

&(((TYPE *) 0)->Field),这里我们用b来取代Filed,0只是一个比较巧妙的构思,先不要在乎是否有意义。由于结构体成员在内存中是由低地址向高地址存储的,这样,&(((TYPE *) 0)->b)实际上就是一个偏移量,这就是用0的巧妙之处。

(CHAR8 *) (Record)就是成员变量b的实际地址了,这里都需要做一个类型(CHAR8 *)的强制转换。

((CHAR8 *) (Record) - (CHAR8 *) &(((TYPE *) 0)->Field))就是结构体的起始地址。然后把整个结果转换成所需要的类型指针,((TYPE *) ((CHAR8 *) (Record) - (CHAR8 *) &(((TYPE *) 0)->Field)))。下面是图片,原理就是这样了。




 

 

 

由handle1遍历到handle2的方法是这样的:IHANDLE * handle=(IHANDLE*)_ CR(handle1 -> ForwardLink , IHANDLE , AllHandles )。

关于EFI_LIST_ENTRY就说的这里了。总结一点就是只要看到LIST_ENTRY,就应该联想到它的链表。像IHANDLE结构体中有两个LIST_ENTRY成员,就应该联想到每个IHANDLE实例处在两条链表中

 

与IHANDLE相关的链表有很多,后面一一牵扯出来。IHANDLE中的AllHandles成员用来链接IHANDLE实例的。这个链表的头部是一个空结点,定义为: _LIST_ENTRY gHandleList。一开始gHandleList->ForwardLink=gHandleList; gHandleList->BackLink=gHandleList。每次IHANDLE都从gHandleList->BackLink插入进来。这时候大家就意识到了这个链表是一个环形双向链表。每当Driver建立一个新的EFI_HANDLE的时候就会插入到这条链表中来。这条链表被称之为handle database。

2.2 由IHANDLE中Protocols引出的链表(struct PROTOCOL_INTERFACE)

再来关注IHANDLE中的Protocols这个成员,它又是指向何方?它指向以PROTOCOL_INTERFACE这个结构体实例。PROTOCOL_INTERFACE定义如下:

(./MdeModulePkg/Core/Dxe/Hand/Handle.h)

typedef struct {

UINTN Signature;

LIST_ENTRY Link; // Link on IHANDLE.Protocols

IHANDLE *Handle; //Back pointer

LIST_ENTRY ByProtocol; // Link on PROTOCOL_ENTRY.Protocols

PROTOCOL_ENTRY *Protocol; // The protocol ID

VOID * Interface; // The interface value

LIST_ENTRY OpenList; // OPEN_PROTOCOL_DATA list

//注意此处如何链接OPEN_PROTOCOL_DATA结构体

UINTN OpenListCount;

} PROTOCOL_INTERFACE;

Driver会为handle添加多个protocol实例,这些实例也是链表的形式存在。PROTOCOL_INTERFACE的link用于连接以IHANDLE为空头结点以PPOTOCOL_INTERFACE为后续结点的链表(注意通过3链表结构图理解)。这个结构体又牵扯出更多的LIST_ENTRY。成员中Handle指向头空结点的这个handle,Protocol指向PROTOCOL_ENTRY这个结构体实例,这个实例存在于另一个链表中,称之为Protocol Database。后面再说这个Protocol Database。先说OpenList引出的链表。

2.2.1 链表OpenList(struct OPEN_PROTOCOL_DATA)

注释中已经说明OpenList引出OPEN_PROTOCOL_DATA list。OPEN_PROTOCOL_DATA定义如下:

(./MdeModulePkg/Core/Dxe/Hand/Handle.h)

typedef struct {

UINTN Signature;

LIST_ENTRY Link; //Link on PROTOCOL_INTERFACE.OpenList

 

EFI_HANDLE AgentHandle;

EFI_HANDLE ControllerHandle;

UINT32 Attributes;

UINT32 OpenCount;

} OPEN_PROTOCOL_DATA;

看到这个结构体就应该想到这个链表的模型了,看到只有一个LIST_ENTRY,这条线路上的链表总算是到头了。

2.2.2 链表Protocol Database(struct PROTOCOL_ENTRY)

PROTOCOL_ENTRY的定义如下:

(./MdeModulePkg/Core/Dxe/Hand/Handle.h)

typedef struct {

UINTN Signature;

LIST_ENTRY AllEntries; // Link Entry inserted to mProtocolDatabase

EFI_GUID ProtocolID; // ID of the protocol

LIST_ENTRY Protocols; // All protocol interfaces

LIST_ENTRY Notify; // Registerd notification handlers

} PROTOCOL_ENTRY;

这个链表也有个头空结点,定义为: LIST_ENTRY mProtocolDatabase。这个链表通过AllEntries这个成员来链接。这里又有几个LIST_ENTRE,这意味着又有好几个链表。

 

3 IHANDLE引出各种链表总结

EFI_HANDLE的定义

EFI_HANDLE定义是这样的:typedef void * EFI_HANDLE。void * 用C语言来理解为不确定类型。它真正的类型是这样定义的:

(./MdeModulePkg/Core/Dxe/Hand/Handle.h)

// 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;//注意此处是如何链接到PROTOCOL_INTERFACE结构体

UINTN LocateRequest;

// The Handle Database key value when this handle was last created or modified

UINT64 Key;

} IHANDLE;

比如定义一个变量EFI_HANDLE hExample,当你将它作为参数传递给service的时候,在service内部是这样使用它的:IHANDLE * Handle=(IHANDLE*)hExample。也就是说IHANDLE*才是handle的本来面目



链表5:关于PROTOCOL_INTERFACE结构体中ByProtocol成员变量。UEFI Spec已经说明一个Protocol对应一个GUID,一个Protocol因不同情况实例化多个实例,所以一个GUID对应多个Protocol的实例。上图中GUID是通过Protocol Database来管理的,而Protocol实例有PROTOCOL_INTERFACE链表来管理,所以ByProtocol成员所在的链表就要以一个链表4中的PROTOCOL_ENTRY中的Protocls成员为头空节点,以PROTOCOL_INTERFACE中的ByProtocol作为后续节点的双向循环链表

实例:假如图中链表1的第一个handle加载有ABC_PROTOCOL实例,第二个handle也加载有ABC_PROTOCOL实例,那么这两个对应的PROTOCOL_INTERFACE实例就会链接到ABC_PROTOCOL_GUID对应的PROTOCOL_ENTRY实例上面。


上图经过抽象后就成了我们经常看到的图,如下:

 





4 以InstallProtocolInterface为例来看handle的内部运作

有了上面的准备后,我就以InstallProtocolInterface这个service来讲述handle的内部运作了。

经过一番顺藤摸瓜后,就会发现InstallProtocolInterface最终的形式是

(./MdeModulePkg/Core/Dxe/Hand/Handle.c)

EFI_STATUS

CoreInstallProtocolInterfaceNotify (

IN OUT EFI_HANDLE *UserHandle,

IN EFI_GUID *Protocol,

IN EFI_INTERFACE_TYPE InterfaceType,

IN VOID *Interface,

IN BOOLEAN Notify

)

{

……

 

 

if (*UserHandle != NULL) {

Status =

CoreHandleProtocol (*UserHandle, Protocol, (VOID **)&ExistingInterface);

//CoreHandleProtocol(...)检索链表1,查看UserHandle是否已存在于handle database中

if (!EFI_ERROR (Status)) {

return EFI_INVALID_PARAMETER;

}

}

……

// Lookup the Protocol Entry for the requested protocol

ProtEntry = CoreFindProtocolEntry (Protocol, TRUE);

//CoreFindProtocolEntry(...)检索链表4,查看GUID是否已经存在于链表中,若不存在在创建一个以参数Protocol为GUID的PROTOCOL_ENTRY实例PortEntry插入链表4中。

……

Handle = (IHANDLE *)*UserHandle;

//露出EFI_HANDLE的本质了,它是(IHANDLE*)。

……

//创建一个Handle及其初始化过程,初始化后插入链表1

if (Handle == NULL) {

Handle = AllocateZeroPool (sizeof(IHANDLE));

if (Handle == NULL) {

Status = EFI_OUT_OF_RESOURCES;

goto Done;

}

 

// Initialize new handler structure

Handle->Signature = EFI_HANDLE_SIGNATURE;

InitializeListHead (&Handle->Protocols);

 

// Initialize the Key to show that the handle has been created/modified

HandleDatabaseKey++;

Handle->Key = gHandleDatabaseKey;

 

// Add this handle to the list global list of all handles in the system

InsertTailList (&gHandleList, &Handle->AllHandles);

}

……

 

}

这样这个函数就介绍的差不多了,这也只是为了做一个引子,像其它有关handle的函数想必也都在这个文件中,头文件的定义很多都在hand.h中,只要有耐心,应该都能看的懂。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值