[整理] Handle & Protocol

出處:http://blog.csdn.net/celiaqianhj/article/details/6764433

Handle Database
 Handle Database是由Handles和Protocols组成的,它是全局的,可以被任何UEFI Image访问。在执行完ExitBootServices()之后,Handle Database就不存在了。

Handle
Handles由一个或多个Protocol组成。
在EFI的初始化过程中,System firmware/UEFI Drivers/UEFI Applications都会创建Handles,并为每个handle挂上一个或多个Protocol。
Boot Service HandleProtocol()和OpenProtocol()是用来在给定的Handle中查找Protocol的。
Handle Type:
Protocol
Protocols是由GUID来命名的数据结构体,它可能是空,可能包含数据,可能包含服务程序,或者同时包含两者。
每个Protocol都必须包含以下三个方面:
1>. The Protocol’s Global Unique ID(GUID)
2>. The Protocol Interface Structure
3>. The Protocol Service
一个UEFI Driver里包含用于实现一个或多个Protocol的Function,并且通过Boo tService InstallProtocolInterface()来注册(Register)。The firmware返回这个Protocol的Interface,这个Interface可以被用来调用(Invoke)这个Protocol的特定的服务(specific services)。
这样,Protocol这种机制就实现了UEFI固件扩展的功能。
=================================================================================
UEFI小结-Handle的来龙去脉

出處:http://www.biosren.com/thread-3440-1-1.html
本文说明:本人刚学习UEFI不久,写该文一是为了将学到的东西做一个规范化的总结,二是为了给初学UEFI的兄弟起到借鉴作用。同样地,错误的地方肯定很多,还望能得到各位弟兄指正。要理解本文,您至少应该是读过UEFI Spec,不然请先阅读UEFI Spec。


一、一些概念的理解
UEFI中会有很多抽象概念,像service、protocol、handle等等,如果将这些抽象的概念放到实际的代码中理解的话,会有更清晰地认识,有了清晰的认识之后再把它们作为抽象来理解,就遂心应手的多了。
首先说protocol,其实它就是一个由struct定义的结构体,这个结构体通常是由数据和函数指针组成,或其一。每个结构体的定义都有一个GUID与之对应。自然并不是所有的结构体都称之为protocol,protocol正如其名,它是一种规范,或称协议。比如要建立一个基于UEFI Driver Model的Driver,就必须要绑定一个EFI_DRIVER_BINGING_PROTOCOL的实例,并且要自定义且实现Support、Start、Stop函数以及填充实例中其他的数据成员。它就相当于已经规范了种种需求和步骤。
再说service,它就是UEFI定义的API函数,所有的service都被集中到EFI_SYSTEM_TABLE下面,都可以通过gST来调用(gST指向一个EFI_SYSTEM_TABLE的全局实例)。
接着本文重点说明handle。
       二、EFI_HANDLE的定义
           EFI_HANDLE定义是这样的:typedef void * EFI_HANDLE。void * 用C语言来理解为不确定类型。它真正的类型是这样定义的(EDKFoundationCoreDxeHandHand.h):
typedef struct {
UINTN        Signature;
EFI_LIST_ENTRY     AllHandles;
EFI_LIST_ENTRY      Protocols;
UINTN        LocateRequest;
UINT64      Key;
} IHANDLE;
比如定义一个变量EFI_HANDLE hExample,当你将它作为参数传递给service的时候,在service内部是这样使用它的:IHANDLE * Handle=(IHANDLE*)hExample。也就是说IHANDLE*才是handle的本来面目。为什么要弄的这么复杂呢?一是为了抽象以隐藏细节,二可能是为了安全。
        三、关于EFI_LIST_ENTRY
要明白IHANDLE这个结构体,就要明白EFI_LIST_ENTRY是如何被使用的。EFI_LIST_ENTRY定义如下(EDKFoundationLibraryDxeIncludeLinkedList.h):
typedef struct _EFI_LIST_ENTRY {
struct    _EFI_LIST_ENTRY    *ForwardLink;
struct    _EFI_LIST_ENTRY    *BackLink;
} EFI_LIST_ENTRY;
大家立刻就会反应到,它用于实现双向链表。但是与一般的链表实现方式不一样,它纯粹是EFI_LIST_ENTRY这个成员的链接,而不用在乎这个成员所在的结构体。一般的链表要求结点之间的类型一致,而这种链表只要求结构体存在EFI_LIST_ENTRY这个成员就够了。比如说IHANDLE *handle1,*handle2;初始化后, handle1->AllHandles->ForwardLink=handle2->AllHandles; handle2->AllHandles->BackLink=handle1->AllHandles。这样handle1与handle2的AllHandles就链接到了一起。但是这样就只能进行AllHandles的遍历了,怎么样遍历IHANLE实例呢?。这时候就要用到_CR宏,_CR宏的定义如下:
#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
由handle1遍历到handle2的方法是这样的:IHANDLE * handle=(IHANDLE*)_ CR(handle1 -> ForwardLink , IHANDLE , AllHandles )。
关于EFI_LIST_ENTRY就说的这里了。总结一点就是只要看到EFI_LIST_ENTRY,就应该联想到它的链表。像IHANDLE结构体中有两个EFI_LIST_ENTRY成员,就应该联想到每个IHANDLE实例处在两条链表中。
      四、各种链表的引出1)由IHANDLEAllHandles引出的链表
与IHANDLE相关的链表有很多,后面一一牵扯出来。IHANDLE中的AllHandles成员用来链接IHANDLE实例的。这个链表的头部是一个空结点,定义为:EFI_LIST_ENTRY   gHandleList。一开始gHandleList->ForwardLink=gHandleList; gHandleList->BackLink=gHandleList。每次IHANDLE都从gHandleList->BackLink插入进来。这时候大家就意识到了这个链表是一个环形双向链表。每当Driver建立一个新的EFI_HANDLE的时候就会插入到这条链表中来。这条链表被称之为handle database。
     2)由IHANDLEProtocols引出的链表
再来关注IHANDLE中的Protocols这个成员,它又是指向何方?它指向以PROTOCOL_INTERFACE这个结构体实例。PROTOCOL_INTERFACE定义如下:
typedef struct {
UINTN     Signature;
         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;
} PROTOCOL_INTERFACE;
Driver会为handle添加多个protocol实例,这些实例也是链表的形式存在。PROTOCOL_INTERFACE的link用于连接以IHANDLE为空头结点以PPOTOCOL_INTERFACE为后续结点的链表。这个结构体又牵扯出更多的EFI_LIST_ENTRY。成员中Handle指向头空结点的这个handle,Protocol指向PROTOCOL_ENTRY这个结构体实例,这个实例存在于另一个链表中,称之为Protocol Database。后面再说这个Protocol Database。先说OpenList引出的链表。
     3)由PROTOCOL_INTERFACEOpenList引出的链表
注释中已经说明OpenList引出OPEN_PROTOCOL_DATA list。OPEN_PROTOCOL_DATA定义如下:
typedef struct {
            UINTN     Signature;
            EFI_LIST_ENTRY    Link;
            EFI_HANDLE     AgentHandle;
             EFI_HANDLE    ControllerHandle;
            UINT32      Attributes;
            UINT32        OpenCount;
} OPEN_PROTOCOL_DATA;
看到这个结构体就应该想到这个链表的模型了,不多说。看到只有一个EFI_LIST_ENTRY,松了一口气,这条线路上的链表总算是到头了。
        4)链表Protocol Database
       PROTOCOL_ENTRY的定义如下:
typedef struct {
UINTN    Signature;
EFI_LIST_ENTRY    AllEntries;   // All entries
EFI_GUID    ProtocolID;    // ID of the protocol
EFI_LIST_ENTRY    Protocols;    // All protocol interfaces
EFI_LIST_ENTRY     Notify;      // Registerd notification handlers
} PROTOCOL_ENTRY;
这个链表也有个头空结点,定义为:EFI_LIST_ENTRY  mProtocolDatabase。这个链表通过AllEntries这个成员来链接。这里又有几个EFI_LIST_ENTRE,这意味着又有好几个链表。这样大家的脑子里可能就乱了。为了对这些链表有清晰的认识,下面是用visio画的简图,省略部分结构体成员,为了不出现飞线,结构体成员位置也挪动了一下。(此图画起来好不容易,我也要署名,呵呵)。
     5)链表综述
恕我唠叨:
图中1表示以gHandleList为头空结点,以EFI_HANDLE实例的AllHandle成员为后续成员结点的环形双向链表;
图中2表示以EFI_HANDLE实例中Protocols成员为头空结点,以PROTOCOL_INTERFACE实例的Link成员为后续成员结点的环形双向链表;
图中3表示以PROTOCOL_INTERFACE实例中的OpenList成员为头空结点,以OPEN_PROTOCOL_DATA实例的Link成员为后续成员结点的环形双向链表(篇幅原因省略一部分)。
图中4表示以mProtocolDatabase为头空结点,以PROTOCOL_ENTRY实例的AllEntries成员为后续成员结点的环形双向链表。
后文直接将它们分别称之为链表1,链表2,链表3,链表4
上面叙述过的链表这里就全部标识出来了,如果把所有的链表都画出来的话,上图就乱了,所有剩下没有标志出来的我就直接叙述了。
链表5:关于PROTOCOL_INTERFACE中的ByProtocol。UEFI spec中已经说一个Protocol对应一个GUID,一个Protocol因不同情况实例化多个实例。所有一个GUID对应着多个Protocol的实例。上图中GUID由Protocol Database来管理,而Protocol实例由PROTOCOL_INTERFACE链表来管理。所以ByProtocol成员所在的链表就要以一个链表4中的PROTOCOL_ENTRY中的Protocols成员为头空结点,以PROTOCOL_INTERFACE中的ByProtocol作为后续结点的双向环链表。比如说图中链表1的第一个handle加载有ABC_PROTOCOL实例,假如第二个handle也加载有ABC_PROTOCOL实例,那么这两个对应的PROTOCOL_INTERFACE实例就会连接到ABC_PROTOCOL_GUID对应的PROTOCOL_ENTRY实例上面。可以想象的到吧?呵呵。
链表6:关于PROTOCOL_ENTRY中的Notify。这就要涉及到新的结构体PROTOCOL_NOTIFY。我觉得有必要在Notity这里打住。
上图经过抽象后就成了我们经常看到的图,如下:

对比之前的链表图,是否对这个图有更清晰的认识呢?
         五、以InstallProtocolInterface为例来看handle的内部运作
有了上面的准备后,我就以InstallProtocolInterface这个service来讲述handle的内部运作了。
经过一番顺藤摸瓜后,就会发现InstallProtocolInterface最终的形式是(EDKFoundationCoreDxeHandHandle.c):
EFI_STATUS
CoreInstallProtocolInterfaceNotify (
IN OUT EFI_HANDLE   *UserHandle,
IN EFI_GUID    *Protocol,
IN EFI_INTERFACE_TYPE    InterfaceType,
IN VOID     *Interface,
IN BOOLEAN   Notify
)
对比与UEFI spec中InstallProtocolInterface的定义,CoreInstallProtocolInterfaceNotify中的Notify为TRUE。这个service的作用就是:当UserHandle为空时,就向handle database中插入新的handle,并且将参数中的Interface所指定的protocol加载到这个handle上面;当UserHandle不为空,就在handle database中找到这个handle,在将这个protocol加载上去。如果通过上面的链表图,你已经想象到了它是如何运作的,那么下文就已经多余了。
代码就不贴了,请直接对照EDK中的代码,从handle.c找到CoreInstallProtocolInterfaceNotify这个函数,想必这个文件大家都有。
同学们,老师要开始讲课了,翻到394行,我念一句,你们跟一句。(呵呵,开玩笑的,哪当得起哦)
462行用CoreHandleProtocol(…)检索链表1,查看UserHandle是否已存在于handle database中。
476行用CoreFindProtocolEntry(…)检索链表4,查看GUID是否已经存在于链表中,若不存在在创建一个以参数Protocol为GUID的PROTOCOL_ENTRY实例PortEntry插入链表4中。
493行露出EFI_HANDLE的本质了,它是(IHANDLE*)。
494行到518行为创建一个handle及初始化它的过程,看仔细了,对理解handle很有用。初始化后就插入到链表1中。
533行到554行,对新创建的PROTOCOL_INTERFACE实例Prot进行初始化,对照链表结构库看仔细了,尤其是各种指针的去向(参数Interface挂接到了Prot下面)。初始化后将Port插入到链表2中。
这样这个函数就介绍的差不多了,这也只是为了做一个引子,像其他有关handle的函数想必也都在这个文件中,头文件的定义很多都在hand.h中,只要有耐心,应该都能看的懂。

=================================================================================
再谈UEFI下的Protocol概念
出處:http://blog.csdn.net/prostar/article/details/4211970
很久写过一个关于protocol的入门文章,但是那个时候说实话写的的确不是很用心。今天在一个论坛看到有朋友又问道protocol如何学习的问题,考虑到protocol是整个UEFI系统的核心所在,我想还是再次重新写一个UEFI protocol的文章吧,希望能够给初学protocol的朋友们一个指路明灯。
Protocol : 为什么要有Protocol
大家都知道,UEFI的英文翻译过来应该叫“可扩展固件接口”。这个名词中最重要的事实上是“可扩展”这三个字。换言之,相比传统的系统固件而言,UEFI固件具备了完善的可扩展支持。这个概念对软件行业不是一个新概念,但是对bios这样一个陈旧腐朽的东西而言,的确是一个创新的思想。
所谓可扩展的含义就是可以在系统完成后(编译为binary)之后,再次为系统增加新的功能,而不用重新rebuild整个系统。在这个大的需求的前提下,还有一些其他的重要的含义,比如必须支持不同的组件的独立开发。比如A厂商今天针对某硬件开发了一个驱动,他们发布了一个二进制包APackage;而B厂商则针对另一个硬件开发了一个驱动,他们发布了一个二进制包BPackage;现在APackage和BPackage都需要集成到bios内。且必须以二进制的形式集成。因为A和B厂商的知识产权需要得到尊重。或者像OS下那样,可以通过某种方法在系统已经运行起来后加载到内存。
更好玩的是,A厂商并不希望自己需要重新从车轮开始发明。比如他们针对的硬件是一个PCI的适配器,他们希望希望系统里已经有了诸如ReadPci()或者WritePci()这样调用来简化他们编程上的工作。换言之,他们希望目标系统能够支持不同的二进制组件之间的运行时通讯。
支持二进制组件的运行时通讯是一个十分复杂的技术。这里所谓的通讯,包括但不限于如下的含义:
1。 互操作。A组件自由可以调用B组件实现的函数。反过来也一样。
2。 数据传递。双方可以通过某种方法(shared memory, pipes and etc)互相交换数据。
3。 可探测。某组件必须具备探测另一个组件是否存在的能力。
4。 组件的开发必须是独立的。开发A组件不需要B组件的源代码,反之亦然。
这可以说是现代操作系统的一个核心概念。熟悉Windows的朋友可能十分清楚Windows下的COM组件,所有的COM组件都可以互相通讯的。且应用程序可以自由的使用任何一个组件提供的服务。
只有具备了这样的能力的系统才可以被叫做“可扩展”的。而UEFI为了达到可扩展的能力,必须提供一种机制来提供支持,于是UEFI领域的专家们发明了Protocol。
Protocol : 从约定到接口
Protocol从本质上说是一种调用者与被调用者之间的“约定”。而这种“约定”在软件开发领域有另一个更形象化的名字叫接口(Interface)。为了做到二进制间的互操作,那么参与操作的双方(调用者与被调用者)都必须做出一定的让步,这个让步就是双方必须遵循实现商量好的调用方法(接口)。而这种事先的约定的接口就是protocol的定义。而这种定义尤以.h文件的形式加以实现。
谈到这里,我插一句,由于目前UEFI还是几乎是用C语言开发,基本上不存在跨语言的问题,所以Protocol才可以表达成.h文件。而Windows下的COM的Interface,由于考虑到跨语言的问题,才专门有了一个新的通用语言叫接口定义语言(IDL,Interface Definiton Language),使用时候专门有一个IDL的compiler将IDL翻译成各个其他语言专用的表达方法(对于C语言,就是生成对应的.h文件)。
我们现在来看一个.h文件,看看一个protocol到底是怎样定义出来的。用任意一个编辑器打开Edl/Foundation/Efi/Protocol/Include/BlockIo.h文件,这个是UEFI规范内Media Access一章中所定义的EFI_BLOCK_IO_PROTOCOL的具体定义。
将关注点首先放到39行。看下面的定义:
typedef
EFI_STATUS
(EFIAPI *EFI_BLOCK_RESET) (
  IN EFI_BLOCK_IO_PROTOCOL          * This,
  IN BOOLEAN                        ExtendedVerification
  );
这是一个指向函数的指针类型的定义,它定义了一个新的指针类型叫EFI_BLOCK_RESET,如此是希望今后的系统能直接使用EFI_BLOCK_RESET类型,这样做主要希望能够提高代码的可读性。这个指针类型指向了一个函数,这个函数有两个输入参数。
之后又利用同样的方法定义了几个其他的函数。下面我们关注一下代码的199行:
typedef struct _EFI_BLOCK_IO_PROTOCOL {
  UINT64              Revision;
  EFI_BLOCK_IO_MEDIA  *Media;
  EFI_BLOCK_RESET     Reset;
  EFI_BLOCK_READ      ReadBlocks;
  EFI_BLOCK_WRITE     WriteBlocks;
  EFI_BLOCK_FLUSH     FlushBlocks;
} EFI_BLOCK_IO_PROTOCOL;
这里又定义了一个大的结构类型EFI_BLOCK_IO_PROTOCOL,注意到这个结构几乎是由之前提及的指针函数所构成的。这样定义是显而易见的,既然是约定的定义,那么自然需要约定的函数的定义。
Protocol : 组成
上面事实上已经说了一下Protocol的最主要的组成部分,约定的定义部分。就是那个大的结构类型,除此之外,一个Protocol还有一些其他的组成部分。
首先是GUID,正如Windows下的COM Interfaces一样,每一个Protocol也有一个定义好的,唯一的标识符。这个标识符就用GUID来表达。这样一来每个Protocol都有了自己的名字,那么使用者可以方便的通过指明不同的GUID来得到不同的Protocol。
其次是一个指向GUID的全局指针变量,比如我们上文引用的那个EFI_BLOCK_IO_PROTOCOL,就有一个全局的指针叫gEfiBlockIoProtocolGuid的指针,他是EFI_GUID *结构的。这样做事实上完全是为了方便,他的变量名的命名规则是统一的,即g + Efi + Protocol 名称 + ProtocolGuid。这样一来,程序员就不需记住相应GUID具体的值,而只在需要GUID作为参数的时候,使用这个全局变量即可,这有点类似Windows下的UUID宏。
=================================================================================

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

EFI的Protocol類似於物件導向中的Interface,它訂定好一個標準,而實作是經由別的程式來完成,而使用也是透過這個標準來使用。
一個Protocol基本上是由數個變數與函式指標所組成的結構,也會跟隨著一個唯一識別的GUID(Globally Unique Identifier),這部分與PEI階段的PEIM中的PPI滿像的,各個不同程式間可利用方法將所需的功能找出來使用。
安裝一個Protocol時會與自己對應的GUID配在一起,放置於Handle Database中,Handle Database是一個Linked List,每個Handle被串在一起,而Handle底下又與屬於它的所有GUID | Protocol相連。
EFI DRIVER BINDING PROTOCOL原型:
typedef struct _EFI_DRIVER_BINDING_PROTOCOL {
 EFI_DRIVER_BINDING_PROTOCOL_SUPPORTED Supported;
 EFI_DRIVER_BINDING_PROTOCOL_START Start;
 EFI_DRIVER_BINDING_PROTOCOL_STOP Stop;
 UINT32 Version;
 EFI_HANDLE ImageHandle;
 EFI_HANDLE DriverBindingHandle;
} EFI_DRIVER_BINDING_PROTOCOL;
其GUID:
#define EFI_DRIVER_BINDING_PROTOCOL_GUID
{0x18A031AB,0xB443,0x4D1A,0xA5,0xC0,0x0C,0x09,0x26,0x1E,0x9F,0x71}
extern EFI_GUID gEfiDriverBindingProtocolGuid = EFI_DRIVER_BINDING_PROTOCOL_GUID;
基本上是使用gEfiDriverBindingProtocolGuid這個變數來與EFI DRIVER BINDING PROTOCOL做關聯。
與Protocol常用的相關服務如下:
InstallMultipleProtocolInterfaces (
 IN OUT EFI_HANDLE *Handle,
 …
 );
安裝數個Protocol與搭配的GUID至指定的Handle底下。
UninstallMultipleProtocolInterfaces (
 IN EFI_HANDLE Handle,
 …
 );
移除數個Handle底下的Protocol與所搭配的GUID。
LocateHandleBuffer (
 IN EFI_LOCATE_SEARCH_TYPE SearchType,
 IN EFI_GUID *Protocol OPTIONAL,
 IN VOID *SearchKey OPTIONAL,
 IN OUT UINTN *NoHandles,
 OUT EFI_HANDLE **Buffer
 );
可將全部或以特定的條件將Handle取出。
ProtocolsPerHandle (
 IN EFI_HANDLE Handle,
 OUT EFI_GUID ***ProtocolBuffer,
 OUT UINTN *ProtocolBufferCount
 );
取得指定的Handle底下所有的Protocol GUID陣列,並傳出數量。
OpenProtocolInformation (
 IN EFI_HANDLE Handle,
 IN EFI_GUID *Protocol,
 OUT EFI_OPEN_PROTOCOL_INFORMATION_ENTRY **EntryBuffer,
 OUT UINTN *EntryCount
 );
取得Handle底下指定的GUID的Information Entry陣列,並回傳數量。
LocateProtocol (
 IN EFI_GUID *Protocol,
 IN VOID *Registration OPTIONAL,
 OUT VOID **Interface
 );
以指定的GUID取出第一個Protocol。
HandleProtocol (
 IN EFI_HANDLE Handle,
 IN EFI_GUID *Protocol,
 OUT VOID **Interface
 );
取出Handle中指定的Protocol,以GUID為識別。
OpenProtocol (
 IN EFI_HANDLE Handle,
 IN EFI_GUID *Protocol,
 OUT VOID **Interface OPTIONAL,
 IN EFI_HANDLE AgentHandle,
 IN EFI_HANDLE ControllerHandle,
 IN UINT32 Attributes
 );
類似HandleProtocol(),但提供了更安全的做法,綁訂指定的Handle,避免使用中的Protocol被Uninstall。
此為最後一個參數的定義:
#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
依需要使用,有些定義可不用CloseProtocol()。
CloseProtocol (
 IN EFI_HANDLE Handle,
 IN EFI_GUID *Protocol,
 IN EFI_HANDLE AgentHandle,
 IN EFI_HANDLE ControllerHandle
 );
為以OpenProtocol()開啟的Protocol解除占用。
========================================================================







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的样子。 

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 有两个成员变量,四个成员函数(当然从C的角度来看,“成员函数”叫法不准确,它实际上也是一个成员变量,只是这个变量是函数指针).  gEfiBlockIoProtocolGuid({0x964e5b21, 0x6459, 0x11d2, {0x8e, 0x39, 0x0, 0xa0, 0xc9, 0x69, 0x72, 0x3b })标示了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(…)获得。

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链表中有该Potocol,Protocol对象的指针写到*Interface,并返回EFI_SUCCESS;否则 返回EFI_UNSUPPORTED 。
如果在驱动中调用OpenProtocol(), AgentHandle是拥有该EFI_DRIVER_BINDING_PROTOCOL对象的Handle;ControllerHandle是拥有该驱动的Controller。
如果调用OpenProtocol的是应用程序,那么AgentHandle是该应用对应的Handle,也就main函数的第一个参数。 ControllerHandle此时可以忽略。

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的简化版,因为大部分情况下我们都不需要关心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(…)是从内核中找出指定Protocol的第一个实例。

typedef
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。

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的数量, Buffer数组由UEFI复杂分配,由用户负责释放。

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相似,只是用户负责分配和释放Buffer数组。

typedef
EFI_STATUS
ProtocolsPerHandle (
IN EFI_HANDLE Handle,
OUT EFI_GUID  ***ProtocolBuffer,
OUT UINTN      *ProtocolBufferCount
);

获得指定Handle所支持的所有Protocol, UEFI负责分配内存给ProtocolBuffer,用户负责释放该内存。

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页有很好的例子演示了怎么打开一个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关闭打开的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因为没有指定AgentHandle,所以无法关闭。如果一定要去关闭它,要调用OpenProtocolInformation()获得AgentHandle和ControllerHandle,然后关闭它。
下面看一个完整的例子,用EFI_DISK_IO_PROTOCOL读取GPT硬盘的分区表

#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_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);
        }
}










未經允許不得轉載:GoMCU » [整理] Handle & Protocol