前一篇笔记里面已经讨论了很多的protocol&handle
的代码本质和底层实现,但是并没有讨论其应用和代表的意义,因为那个时候还不能理解。经过一段时间的学习之后,本菜鸟仍然不能说完全理解了protocol&handle
,就像一位深耕多年的前辈说的:“UEFI中protocol还是比较容易,但是handle想要理解确实很难。”既然他们都说很难,那我也不和自己较劲,死磕没意思,我觉得深入理解一个东西的本质还是应用,因此还是先自己写一个最简单的代码,然后由此展开由点到面的学习和发展。
(由于本人完全新手脑子也没有那么灵光 所以一切都基于本人的学习方式和学习进度 如有错误请大家指正)
那么现在首先让我们自己定义、安装和使用一个最最简单的protocol
。在介绍代码的同时,我也会将自己的心得以及后续需要弥补的东西写出来,一个新人,肯定不能面面俱到,大家海涵。
前面说过,protocol的结构体中可能包含了data/service结构体,那么我们来实现一个最简单的protocol,包含这两种形式的成员。
具体代码如下:
//TestProtocol.h
#ifndef __HELLO_WORLD_PROTOCOL_H__
#define __HELLO_WORLD_PROTOCOL_H__
#include
#define EFI_HELLO_WORLD_PROTOCOL_GUID \
{0x039d1af8, 0x1c8d, 0x408f, { 0x59, 0x25, 0x30, 0x4f, 0x5b, 0x96, 0x5d, 0x6e }}
typedef struct _EFI_HELLO_WORLD_PROTOCOL EFI_HELLO_WORLD_PROTOCOL;
typedef
EFI_STATUS
(EFIAPI *HELLOWORLD) (
IN EFI_HELLO_WORLD_PROTOCOL *This
);
struct _EFI_HELLO_WORLD_PROTOCOL {
UINTN Version;
HELLOWORLD HelloWorld;
};
extern EFI_GUID gEfiHelloWorldProtocolGuid;
#endif
解释一下代码中我觉得需要说明的地方
首先就是_EFI_HELLO_WORLD_PROTOCOL
结构体,这就是我们定义的 protocol
,可以看到protocol
成员有两个
Version
:data类型,保存基本的信息HelloWorld
:service类型,用以实现某种功能。在实现的时候,其中version
就是简单的赋值即可,而第二个HelloWorld
成员的类型比较特殊,可以看到是一个HELLOWORLD
类型的,这个类型在文件中已经定义了。这个成员是一个函数指针,也就是在实现的时候,该成员会指向某一个具体的函数。
函数的入参只有一个类型为EFI_HELLO_WORLD_PROTOCOL
的this
指针,这个位置需要注意,这个this
指针就是指向使用函数的protocol
结构体自身。而且,所有的protocol
的service函数的首个参数,都是指向自己结构体的指针(这是必须的)
[了解C++的同学可以回想一下C++中类对象使用的时候,也是有一个this指针的,这里的this指针和那个this指针异曲同工,这样我觉得能更容易理解]
其他的参数如果需要可以正常的添加,由于这里我们只讨论最简单的形式,所以只写了这一个必要的参数。
其次我们来看protocol
的GUID定义。在 这个.h
文件中,我们定义了EFI_HELLO_WORLD_PROTOCOL
gEfiHelloWorldProtocolGuid
和EFI_HELLO_WORLD_PROTOCOL_GUID
,这三者的具体联系是什么样子的呢?在代码中为什么我们能够通过gEfiHelloWorldProtocolGuid
去定位到所需的protocol?其实底层的原理我目前也还不清楚,( 不清楚很正常啊 新人学习 总有不清楚的东西 )只能先说一下我当前的理解:
EFI_HELLO_WORLD_PROTOCOL
是常见的C语言的定义,无需多言。EFI_HELLO_WORLD_PROTOCOL_GUID
就是这个protocol
的一个身份号码,让主程序能够通过身份证号找到目标protocol
。这个身份证号的声明就是在结构体定义后面加上_GUID
,目前我还没有遇到过其他声明的方式。gEfiHelloWorldProtocolGuid
是一个extern
的变量,此处仅做了一个声明,并没给有进行定义,那么定义在什么地方呢?答:dec文件中。可以看到,在.dec
文件中我将使用到的GUID在这个文件中进行声明,这样就能够保证其他文件调用的时候能够找到对应的protocol
。仔细观察就能发现,这个位置将 gEfiHelloWorldProtocolGuid
声明成了和EFI_HELLO_WORLD_PROTOCOL_GUID
相同的数据,这个数据是一个128bit的纯数字,这不二者就联系起来了。
既然都说到这里了,顺便看一下GUID定义吧(这个东西你说不重要吧,处处用到,你说重要吧,平时又不会直接用数字表示)
至此,定义的.h
文件我觉得已经说完了。
针对和我一样的超级新手,提前在这里说一句,如果你想运行起来这篇文章中的代码,除了需要自己创建.c
.h
.inf
之外,还需要将这些文件按照一定的结构进行保存同时在dec
dsc
中进行声明。
我当前的文件结构情况是:将.c
.h
.inf
保存在同一个文件夹中,需要声明的protocol保存在 EmulatorPkg\Include\Protocol目录下,同时在分别在 EmulatorPkg.dec
EmulatorPkg.dsc
进行声明:
EmulatorPkg.dec
的protocol
块下进行声明(前面已经说过了)
EmulatorPkg.dsc
的Components
块下进行声明:
这样就将我们定义好的文件包含进了UEFI的代码中。
前述.h
文件已经将protocol
的定义完成,那么想要实现protocol
的基本功能,就一定需要将定义中的成员实现。同时protocol
想要发挥自己的作用,就必须做到
protocol
的实现和安装// TestProtocolInstall.c
#include
#include
#include
#include
#include
#include
EFI_STATUS
EFIAPI
HelloWorld(
IN EFI_HELLO_WORLD_PROTOCOL *This
)
{
DEBUG ((EFI_D_ERROR, "Hello World\n"));
return EFI_SUCCESS;
}
EFI_STATUS
EFIAPI
TestProtocolInstallEntry (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
EFI_HELLO_WORLD_PROTOCOL *Protocol;
Protocol = AllocatePool (sizeof (EFI_HELLO_WORLD_PROTOCOL));
if (NULL == Protocol) {
DEBUG ((EFI_D_ERROR, "[TestProtocol][%s][%d]:Out of resource.",__FUNCTION__, __LINE__));
return EFI_OUT_OF_RESOURCES;
}
Protocol->Version = 0x6E;
Protocol->HelloWorld=HelloWorld;
Status = gBS->InstallProtocolInterface (
&ImageHandle,
&gEfiHelloWorldProtocolGuid,
EFI_NATIVE_INTERFACE,
Protocol
);
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "[TestProtocol]Install EFI_HELLO_WORLD_PROTOCOL failed. - %r\n", Status));
FreePool (Protocol);
return Status;
}
return EFI_SUCCESS;
}
在 HelloWold Application 那篇文章中,我总结了一个模板,可以看到,在这里仍然是适用的。入口函数的入参和返回值仍然符合我之前说的情况,就不重复,详细的看一下函数的body。
函数为名为Protocol
的指针申请了对应的内存(利用AllocatePool
函数 ),同时将其成员进行了赋值,Version
值为0x6E
,函数指针指向了一个名为HelloWorld
的函数,而这个HelloWorld
函数,可以看到只实现了一个功能就是输出一行HelloWorld
。
接着也是UEFI中最最最常见的操作,插入protocol
,利用到的Service为InstallProtocolInterface
,关于UEFI 的 service可以看一下这里进行了简单的介绍。这个函数是专门用来插入protocol instance的,注意传入参数是一个protocol instance 的 pointer。
[关于这个service的使用和详细介绍,我预计下一篇文章会说一说 。其实我自己的笔记上已经写了很多东西 但是整理还需要时间 而且真的很长很长 我也不知道我的介绍思路对不对]
至此我们就完成了protocol
的插入,根据之前的经验我们还需要一个inf
文件以生成对应的.efi
[Defines]
INF_VERSION = 0x00010015
BASE_NAME = TestProtocolInstall
FILE_GUID = 16BEFBED-60DC-4EA2-8E81-A343DF6C2117
MODULE_TYPE = UEFI_DRIVER
VERSION_STRING = 1.0
ENTRY_POINT = TestProtocolInstallEntry
[Sources]
TestProtocolInstall.c
[Packages]
MdePkg/MdePkg.dec
MdeModulePkg/MdeModulePkg.dec
EmulatorPkg/EmulatorPkg.dec
[LibraryClasses]
UefiDriverEntryPoint
UefiBootServicesTableLib
MemoryAllocationLib
DebugLib
[Protocols]
gEfiHelloWorldProtocolGuid
[Depex]
TRUE
inf
文件中,需要注意的一点是,这次我将MODULE_TYPE
的类型定义成了UEFI_DRIVER
,而不是UEFI_APPLICATION
。驱动和应用程序的最大区别就是驱动会常驻内存,而应用程序执行完毕之后就会从内存中清除。我这次写了一个普通的 Driver,(不是UEFI Driver ,两者的主要区别就是是否符合UEFI Driver Model )主要是确实 driver是实际代码中使用最多的情况,后面也会详细的进行介绍。(也已经在写了 挖的坑越来越多 = = )这里只是需要注意一下,inf
文件中有一些相应的变动的位置。
最后是protocol使用情况,代码如下:
#include
#include
#include
#include
#include
EFI_STATUS
EFIAPI
TestProtocolUseEntry (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
EFI_HELLO_WORLD_PROTOCOL *Protocol;
Status = gBS->LocateProtocol (&gEfiHelloWorldProtocolGuid, NULL, (VOID **)&Protocol);
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "[TestProtocol]Locate EFI_HELLO_WORLD_PROTOCOL failed. - %r\n", Status));
return Status;
}
DEBUG ((EFI_D_ERROR, "Protocol Version: 0x%08x\n", Protocol->Version));
Status = Protocol->HelloWorld (Protocol);
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "[TestProtocol]Protocol->Hello failed. - %r\n", Status));
return Status;
}
return EFI_SUCCESS;
}
代码中首先去查找使用到的protocol
,调用的函数为LocateProtocol
,这个函数我会在下一次统一介绍。找到protocol之后,首先输出了一下Version
,然后,调用函数HelloWorld
。
对应的inf
代码:
[Defines]
INF_VERSION = 0x00010015
BASE_NAME = TestProtocolUse
FILE_GUID = 16BAFDED-60DC-4EA2-8E81-A343DA6C2119
MODULE_TYPE = UEFI_DRIVER
VERSION_STRING = 1.0
ENTRY_POINT = TestProtocolUseEntry
[Sources]
TestProtocolUse.c
[Packages]
MdePkg/MdePkg.dec
MdeModulePkg/MdeModulePkg.dec
EmulatorPkg/EmulatorPkg.dec
[LibraryClasses]
UefiDriverEntryPoint
UefiBootServicesTableLib
MemoryAllocationLib
DebugLib
[Protocols]
gEfiHelloWorldProtocolGuid
[Depex]
TRUE
同样,MODULE_TYPE
的类型定义成了UEFI_DRIVER
,而不是UEFI_APPLICATION
。
实现上述文件之后,编译代码即可。
由于之前我们将MODULE_TYPE
的属性定义成了UEFI_DRIVER
,所以加载的过程可能有点不同。需要打开fs0,然后使用load命令进行加载。
成功!!!
这一篇文章整体很简单,就是实现了一个最最简单的protocol
,我是希望用这段简单的代码引出后面的两个问题:UEFI DRIVER和 gBs service。后面的叙述都是由以此protocol
的实现引出的问题而展开的,所以这是篇承上启下的文章,是思考的结点,这也是我本人在学习的时候的顺序。我觉得这是一个符合思路的顺序 所以在笔记的顺序我也是这样写的,我觉得新人 总不能上来就什么都知道,记录下自己思考的顺序给他人一点启示是很好的。
前一阵遇到了很多的事情,感觉整个人心情down到了地下十八层,好不容易才稍微缓和。