UEFI开发探索42 – Protocol的使用1

(请保留->发布地址: http://yiiyee.cn/blog/author/luobing/ )

虽然一直使用各种Protocol来实现需要的程序功能,但对其背后的原理、实现方法,一直都比较模糊。我奉行的是“先用再说”的实用主义,正好周末有点闲暇,探究一下对Protocol理解模糊的地方。

图1 Protocol的构成

如图为Protocol的结构图,摘自于UEFI Spec 2.8 page 45。

我想弄清楚的问题如下:

1) 如何使用Protocol服务?
2) Protocol这种机制在UEFI中是如何实现的?
3) 如何实现一个Protocol?

1 函数学习

与Protocol处理有关的函数,在Spec中给出了列表:

图2 相关的函数

如果只是使用Protocol,上述的很多函数都不需要关注。简单描述下需要用到的几个函数:

1-1 OpenProtocol()

typedef EFI_STATUS (EFIAPI *EFI_OPEN_PROTOCOL) (
IN EFI_HANDLE Handle,  //指定要打开此Handle安装的Protocol接口
IN EFI_GUID *Protocol,  //要打开的Protocol(指向该Protocol GUID的指针)
OUT VOID **Interface OPTIONAL, //返回打开的Protocol对象,如果没有则返回NULL
IN EFI_HANDLE AgentHandle, //打开此Protocol的Image(对UEFI Application
IN EFI_HANDLE ControllerHandle, //如果打开的Protocol是符合UEFI Driver Model的driver,
                 
此参数为控制Protocol接口的控制器,否则为可选的,并且可能为NULL
IN UINT32 Attributes //打开Protocol的参数
);

函数描述:

上述的几个参数,Handle是UEFI中设备的对象,它是Protocol的提供者。如果Handle的Protocol链表中有该Protocol,则Protocol对象的指针将写到*Interface中(注意,Interface是个指向指针的指针)。

在驱动中调用OpenProtocol(),则ControllerHandle是拥有该驱动的控制器,即请求使用这个Protocol的控制器; AgentHandle是拥有该EFI_DRIVER_BINGDING_PROTOCOL对象的Handle。如果打开的是应用程序(UEFI Application),则AgentHandle是该程序的Handle,即UefiMain函数的第一个参数,ControllerHandle可以忽略。

该函数如果返回错误,有很多种可能性。具体可以参考Spec(UEFI spec 2.8 page 187)。

Attributes有6个值可以使用:

#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

可根据实际情况自由选择上述参数。

1-2 HandleProtocol()

typedef EFI_STATUS (EFIAPI *EFI_HANDLE_PROTOCOL) (
IN EFI_HANDLE Handle, //需要查询的Handle,看是否支持指定的Protocol
IN EFI_GUID *Protocol, //发布的唯一Protocol标志,也即指向有效Protocol GUID的指针
OUT VOID **Interface //返回待查询的Protocol
);

函数描述:

它是OpenProtocol()的简化版,不需要指定AgentHandle、ControllerHandle和Attributes的值(OpenProtocol()中的三个参数)。实际上,在函数内部中还是使用了OpenProtocol(),AgentHandle为gDxeCoreImageHandle,ControllerHandle使用NULL值,Attributes则使用EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL。

1-3 LocateHandleBuffer()

typedef EFI_STATUS (EFIAPI *EFI_LOCATE_HANDLE_BUFFER) (
IN EFI_LOCATE_SEARCH_TYPE SearchType, //指定哪种handle返回,即设定查找方式
IN EFI_GUID *Protocol OPTIONAL, //指定的Protocol(GUID),此参数只有在SearchType
                              ByProtocol
时才有效
IN VOID *SearchKey OPTIONAL, //依SearchType不同,提供搜索的关键字
IN OUT UINTN *NoHandles, //返回找到的Handle数量
OUT EFI_HANDLE **Buffer //分配Handle数组并返回
);

函数描述:

此函数返回一个或多个Handle,以匹配SeatchType中规定的请求。参数Buffer所需要的内存,由函数使用EFI_BOOT_SERVICES.AllocatePool()分配,使用完后,调用者必须调用FreePool()将其释放。

SearchType有三种类型:

AllHandles: 参数Protocol和SearchKey会被忽略,返回系统中的所有Handle;
ByRegisterNotify: 从RegisterProcotolNotify中找出匹配SearchKey的Handle,Protocol参数会被忽略;
ByProtocol: 从系统Handle数据库中找出支持指定Protocol的Handle,SearchKey参数会被忽略。

1-4 LocateProcotrol()

typedef EFI_STATUS (EFIAPI *EFI_LOCATE_PROTOCOL) (
IN EFI_GUID
*Protocol, //待查询的Protocol
IN VOID
*Registration OPTIONAL, //可选参数,从RegisterPtotocolNotify()获得注册Key
OUT VOID
**Interface //返回系统中第一个匹配到的Protocol接口
);

函数描述:

此函数找到第一个支持Protocol的设备Handle,并返回其Protocol接口(Protocol Interface)。调用时不用指定Handle,相比较OpenProtocol()和HandleProtocol(),更为简便。

1-5 OpenProtocolInformation()

typedef EFI_STATUS (EFIAPI *EFI_OPEN_PROTOCOL_INFORMATION) (
IN EFI_HANDLE Handle, //已经查询到的Protocol的设备句柄
IN EFI_GUID *Protocol, //待查询的Protocol(GUID)
OUT EFI_OPEN_PROTOCOL_INFORMATION_ENTRY **EntryBuffer, //查询到的信息从此参数返
                                                        

OUT UINTN *EntryCount  //EntryBuffer数组元素个数
);

函数描述:

此函数用于获得指定设备上指定Protocol的打开信息。它会为EntryBuffer分配内存,并将返回的信息存入其中,内存的释放由调用者完成。EntryBuffer的数据结构如下:

typedef struct {
EFI_HANDLE AgentHandle;
EFI_HANDLE ControllerHandle;
UINT32 Attributes;
UINT32 OpenCount;
} EFI_OPEN_PROTOCOL_INFORMATION_ENTRY

返回的信息包括AgentHandle、ControllerHandle、打开的属性和打开个数。很多时候,这个函数获取的信息用来关闭打开的Protocol。

1-6 CloseProtocol()

typedef EFI_STATUS (EFIAPI *EFI_CLOSE_PROTOCOL) (
IN EFI_HANDLE Handle, //之前已经打开的Protocol接口的设备句柄,准备关闭
IN EFI_GUID *Protocol, //发布的唯一Protocol标志,也即指向有效Protocol GUID的指针
IN EFI_HANDLE AgentHandle, //打开此Protocol的Image(对UEFI Application
IN EFI_HANDLE ControllerHandle, //如果打开的Protocol是符合UEFI Driver Model的driver,
                 
此参数为控制Protocol接口的控制器,否则为可选的,并且可能为NULL
);

函数描述:

Protocol使用完毕后要通过此函数来关闭。通过HandleProtocol()和LocateProtocol()打开的Protocol没有指定AgentHandle,没法直接关闭。需要调用OpenProtocolInformation()获得AgentHandle和ControllerHandle,然后再关闭。

其他的一些函数不一一列举了,需要的时候再去读Spec,可在Spec 7.3 Protocol Handler Services中找到相应的内容。

2 使用Protocol

为了熟悉Protocol的使用,写了一个小程序,用来判断Protocol是否存在。

实际上,在使用各种Protocol的过程中,其中最重要的一点,就是UEFI下要提供相应的支持。如同之前在Legacy BIOS下的开发一样,比如鼠标,如果BIOS对鼠标的中断不支持,那希望在自己的代码中支持鼠标,工作量太大了。

为了方便显示,我把之前编写的汉字显示代码(探索系列博客18)移植到了UefiMain为入口的代码中。期间也遇到点小问题,在系列博客相关的问题集中也描述过,这里不再赘述。

代码比较简单。实际上,读过上面函数的说明,代码也就不难编写了。

我主要的目的,对传入的Protocol GUID进行定位,看系统中是否存在相应的Protocol,以及相关的Handle的数目。

具体来说,以串口为例,也就是找SerialProtocol是否存在,以及存在多少个串口。实际代码都在本章例子TryProtocol.c的函数ListProtocolMsg()中,如下:

图3 Protocol信息枚举

另外,在平常的使用中,我处理Protocol的代码都在Common.c中。需要注意的是,代码中的一些细节还没有处理好,比如内存的释放、Protocol使用之后的关闭等。

商用化的时候再处理吧。

3 演示

我用上面的函数来检查串口和随机数生成器的Protocol,编译之后,显示如下:

图4 程序演示

在TianCore的模拟环境中,有两个串口、没有提供随机数生成器的Protocol,一如测试结果所示。

对于其他函数的使用,网上也有大量的例子可供参考,我也没精力一个个试验了。准备进入下一个问题,如何生成Protocol了。

百度云链接:https://pan.baidu.com/s/1gccSosw8_UAGTI5gZPnLCA
提取码:dx23
文件在 FF RobinPkg/ RobinPkg /Applications/ TryProtocol 下

68 total views, 3 views today

发表评论

电子邮件地址不会被公开。 必填项已用*标注