UEFI——Protocol应用(简单的Driver)

前言

前一篇笔记里面已经讨论了很多的protocol&handle的代码本质和底层实现,但是并没有讨论其应用和代表的意义,因为那个时候还不能理解。经过一段时间的学习之后,本菜鸟仍然不能说完全理解了protocol&handle ,就像一位深耕多年的前辈说的:“UEFI中protocol还是比较容易,但是handle想要理解确实很难。”既然他们都说很难,那我也不和自己较劲,死磕没意思,我觉得深入理解一个东西的本质还是应用,因此还是先自己写一个最简单的代码,然后由此展开由点到面的学习和发展。
(由于本人完全新手脑子也没有那么灵光 所以一切都基于本人的学习方式和学习进度 如有错误请大家指正)
那么现在首先让我们自己定义、安装和使用一个最最简单的protocol。在介绍代码的同时,我也会将自己的心得以及后续需要弥补的东西写出来,一个新人,肯定不能面面俱到,大家海涵。

Protocol定义

前面说过,protocol的结构体中可能包含了data/service结构体,那么我们来实现一个最简单的protocol,包含这两种形式的成员。
具体代码如下:

//TestProtocol.h
#ifndef __HELLO_WORLD_PROTOCOL_H__
#define __HELLO_WORLD_PROTOCOL_H__
 
#include <Uefi.h>
 
#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成员有两个

  1. Version:data类型,保存基本的信息
  2. HelloWorld:service类型,用以实现某种功能。

在实现的时候,其中version就是简单的赋值即可,而第二个HelloWorld成员的类型比较特殊,可以看到是一个HELLOWORLD类型的,这个类型在文件中已经定义了。这个成员是一个函数指针,也就是在实现的时候,该成员会指向某一个具体的函数。
函数的入参只有一个类型为EFI_HELLO_WORLD_PROTOCOLthis指针,这个位置需要注意,这个this指针就是指向使用函数的protocol结构体自身。而且,所有的protocol的service函数的首个参数,都是指向自己结构体的指针(这是必须的
[了解C++的同学可以回想一下C++中类对象使用的时候,也是有一个this指针的,这里的this指针和那个this指针异曲同工,这样我觉得能更容易理解]
其他的参数如果需要可以正常的添加,由于这里我们只讨论最简单的形式,所以只写了这一个必要的参数。
其次我们来看protocol的GUID定义。在 这个.h文件中,我们定义了EFI_HELLO_WORLD_PROTOCOL gEfiHelloWorldProtocolGuidEFI_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.decprotocol块下进行声明(前面已经说过了)
EmulatorPkg.dscComponents块下进行声明:
在这里插入图片描述

这样就将我们定义好的文件包含进了UEFI的代码中。


protocol实现与安装

前述.h文件已经将protocol的定义完成,那么想要实现protocol的基本功能,就一定需要将定义中的成员实现。同时protocol想要发挥自己的作用,就必须做到

  1. 安装ptorocol
  2. 使用protocol
    下面的文件实现了protocol的实现和安装
// TestProtocolInstall.c
#include <Uefi.h>
#include <Library/UefiDriverEntryPoint.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/DebugLib.h>
#include <Protocol/TestProtocol.h>
 

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的使用

最后是protocol使用情况,代码如下:

#include <Uefi.h>
 
#include <Library/UefiDriverEntryPoint.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/DebugLib.h>
 
#include <Protocol/TestProtocol.h>
 

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命令进行加载。

  1. 打开fs0在这里插入图片描述

  2. 加载InstalProtocol的driver在这里插入图片描述
    在这里插入图片描述

  3. 使用protocol在这里插入图片描述
    在这里插入图片描述

成功!!!

总结

这一篇文章整体很简单,就是实现了一个最最简单的protocol,我是希望用这段简单的代码引出后面的两个问题:UEFI DRIVER和 gBs service。后面的叙述都是由以此protocol的实现引出的问题而展开的,所以这是篇承上启下的文章,是思考的结点,这也是我本人在学习的时候的顺序。我觉得这是一个符合思路的顺序 所以在笔记的顺序我也是这样写的,我觉得新人 总不能上来就什么都知道,记录下自己思考的顺序给他人一点启示是很好的。

  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值