当前位置: 首页 > article >正文

UEFI开发——编写一个简单的PPI

PEI阶段是由一个个的PEIM实现的,而PPI是PEIM之间互相调用的接口。简单来说,PPI只是一个接口,接口里面有成员函数,想调用某个函数就必须通过该接口。

PPI通过PPI描述符来描述其特性,PPI描述符是一个数据结构,定义如下:

typedef struct {
  //UINTN是一个无符号的整数,Flags是一系列诸如EFI_PEI_PPI_DESCRIPTOR_***的组合
  UINTN       Flags;
  //接口的名字,即它的GUID
  EFI_GUID    *Guid;
  //指向PPI的指针,包含ppi的实例信息
  VOID        *Ppi;
} EFI_PEI_PPI_DESCRIPTOR;

在利用PPI的过程中,有个几个重要的PPI服务:

InstallPpi():安装PPI到PEI foundation

LocatePpi():根据PPI名字GUID从PEI foundation中找到Interface

NotifyPpi():PPI里的function不会再派发时就执行,会有一个判定条件,通知系统这个PPI会在某个PPI被安装时才执行。

1、编写一个InstallPPI的源文件:

该模块通过PeiServicesInstallPpi注册接口,以提供服务

/**
  This service enables a given PEIM to register an interface into the PEI Foundation.

  @param  PpiList               A pointer to the list of interfaces that the caller shall install.

  @retval EFI_SUCCESS           The interface was successfully installed.
  @retval EFI_INVALID_PARAMETER The PpiList pointer is NULL.
  @retval EFI_INVALID_PARAMETER Any of the PEI PPI descriptors in the list do not have the
                                EFI_PEI_PPI_DESCRIPTOR_PPI bit set in the Flags field.
  @retval EFI_OUT_OF_RESOURCES  There is no additional space in the PPI database.

**/

//使特定的PEIM向PEI foundation注册接口
EFI_STATUS
EFIAPI
PeiServicesInstallPpi (
  IN CONST EFI_PEI_PPI_DESCRIPTOR  *PpiList //指向调用者应安装的接口列表的指针
  );

 所提供的服务由EFI_PEI_PPI_DESCRIPTOR这个数据结构通过PEIM向PEI foundation进行描述,这个数据结构的原型为

// The data structure through which a PEIM describes available services to the PEI Foundation.
typedef struct {
  // This field is a set of flags describing the characteristics of this imported table entry.
  // All flags are defined as EFI_PEI_PPI_DESCRIPTOR_***, which can also be combined into one.
  UINTN       Flags;
  // The address of the EFI_GUID that names the interface.
  EFI_GUID    *Guid;
  // A pointer to the PPI. It contains the information necessary to install a service.
  VOID        *Ppi;
} EFI_PEI_PPI_DESCRIPTOR;

完整的 MyHelloWorldInstallPPI模块的源文件为:

#include <uefi.h>
#include <Library/UefiLib.h>
#include <Library/BaseLib.h>
#include <Library/DebugLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/UefiDriverEntryPoint.h>
#include <Library/PeimEntryPoint.h>
#include <Library/PeiServicesLib.h>
#include <Library/PeiServicesTablePointerLib.h>
#include <Pi/PiHob.h>
#include <Pi/PiPeiCis.h>

EFI_GUID gMyHelloWorldPEIGUID = { 0xbdb38129, 0x4d65, 0x39f4, { 0x72, 0x12, 0x68, 0xcf, 0x5a, 0x19, 0xa, 0xf8 }};

//定义了一个DEBUG_ERROR级别的信息打印函数,
EFI_STATUS
EFIAPI
PrintHelloMsg (
  IN CHAR16 * Msg //定义了一个输入参数,类型为CHAR16的指针
  )
{
  DEBUG ((EFI_D_ERROR, "[MyHelloWorldPPI] PrintHelloMsg : %s \n",Msg));
  return EFI_SUCCESS;
}

/*
typedef的作用是给已有的数据类型或结构体定义一个新的名称
函数指针:指向函数的指针变量
int maxValue (int a, int b) {
    return a > b ? a : b;
}    
 
int (*p)(int, int) = NULL;  //定义一个与maxValue兼容的指针
p = maxValue;
p(20, 45);  //通过指针调用
*/
typedef EFI_STATUS (EFIAPI * PRINTMSG)(CHAR16 *Msg);//给已有的数据类型定义了一个新的名称PRINTMSG,原来的数据类型为函数指针类型EFI_STATUS (EFIAPI *)(CHAR16* Msg)
typedef struct _EFI_HELLOWORLD_PROTOCOL  {
    PRINTMSG PrintMsg;
}EFI_HELLOWORLD_PROTOCOL;//定义了一个结构体,结构体成员为函数指针PrintMsg,并使用typedef将这个结构体的别名为EFI_HELLOWORLD_PROTOCOL

//ppi实例
EFI_HELLOWORLD_PROTOCOL mHelloWorldPpi = {
    PrintHelloMsg
};//定义了一个EFI_HELLOWORLD_PROTOCOL类型的全局变量,并赋初值为PrintHelloMsg(即指向上面的打印函数)

EFI_PEI_PPI_DESCRIPTOR     mPpiListCodePpi = { //EFI_PEI_PPI_DESCRIPTOR是一个数据结构,可以向PEIfoundation描述能够提供的服务
  (EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST), //EFI_PEI_PPI_DESCRIPTOR_***这样flag的组合,| 表示按位或
  &gMyHelloWorldPEIGUID, //GUID, PPI的名字
  &mHelloWorldPpi //ppi实例
};//在这里定义了一个结构体变量mPpiListCodePpi,并进行初始化

//安装PPI,指的是把一个或多个PPI注册到PEI foundation中
EFI_STATUS
EFIAPI
MyHelloWorldInstallPPIEntry(
  IN       EFI_PEI_FILE_HANDLE  FileHandle,
  IN CONST EFI_PEI_SERVICES     **PeiServices //指向EFI_PEI_SERVICES table的间接指针
)
{
  EFI_STATUS                              Status = EFI_SUCCESS;
  DEBUG ((EFI_D_ERROR, "[MyHelloWorldPPI] MyHelloWorldInstallPPIEntry Start..\n"));
  PeiServicesInstallPpi  (&mPpiListCodePpi);
  DEBUG ((EFI_D_ERROR, "[MyHelloWorldPPI] MyHelloWorldInstallPPIEntry End..\n"));

  return Status;
}

2、编写InstallPPI的inf文件

3、编写LocatePPI的源文件:

 PeiServicesLocatePpi服务的作用让PEIM发现接口的给定实例,其原型为:

EFI_STATUS
EFIAPI
PeiServicesLocatePpi (
  IN CONST EFI_GUID              *Guid, //指向需要被查找的GUID
  IN UINTN                       Instance, //所需接口的第几个实例
  IN OUT EFI_PEI_PPI_DESCRIPTOR  **PpiDescriptor  OPTIONAL, //输出参数,指向EFI_PEI_PPI_DESCRIPTOR实例的指针
  IN OUT VOID                    **Ppi //这是一个输出参数,指向接口实例的指针
  );
#include <uefi.h>
#include <Library/UefiLib.h>
#include <Library/BaseLib.h>
#include <Library/DebugLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/UefiDriverEntryPoint.h>
#include <Library/PeimEntryPoint.h>
#include <Library/PeiServicesLib.h>
#include <Library/PeiServicesTablePointerLib.h>
#include <Pi/PiHob.h>

EFI_GUID gMyHelloWorldPEIGUID = { 0xbdb38129, 0x4d65, 0x39f4, { 0x72, 0x12, 0x68, 0xcf, 0x5a, 0x19, 0xa, 0xf8 }};


typedef EFI_STATUS (EFIAPI * PRINTMSG)(CHAR16 *Msg);
typedef struct _EFI_HELLOWORLD_PROTOCOL  {
    PRINTMSG PrintMsg;
}EFI_HELLOWORLD_PROTOCOL;

//ShellCEntryLib call user interface ShellAppMain
EFI_STATUS
EFIAPI
MyHelloWorldLocatePPIEntry(
  IN       EFI_PEI_FILE_HANDLE  FileHandle,
  IN CONST EFI_PEI_SERVICES     **PeiServices
)
{
  EFI_STATUS  Status = EFI_SUCCESS;
  EFI_HELLOWORLD_PROTOCOL *mHelloWorldPpi = NULL;
  DEBUG ((EFI_D_ERROR, "[MyHelloWorldPPI] MyHelloWorldLocatePPIEntry Locate PPI Start..\n"));
//定位PPI接口
  Status = PeiServicesLocatePpi (
           &gMyHelloWorldPEIGUID, //我们要查找的PPI的GUID
           0,
           NULL,
           (VOID **)&mHelloWorldPpi //输出参数,指向PPI实例指针的指针
           );
  if (EFI_ERROR(Status)){
      DEBUG ((EFI_D_ERROR, "[MyHelloWorldPPI] MyHelloWorldLocatePPIEntry Locate PPI Fail..%r\n",Status));
      return Status;
  }
  mHelloWorldPpi->PrintMsg(L"2019 CSDN Locate PPI Hello World ...\n");
  DEBUG ((EFI_D_ERROR, "[MyHelloWorldPPI] MyHelloWorldLocatePPIEntry Locate PPI End..\n"));
  return Status;
}

4、编写LocatePPI的inf文件

编译整个包,并将生成的OVMF.fd文件加载到qemu中,其

注意这里的模块是PEIM,无法在qemu里运行其二进制文件

制作运行qemu的脚本,新建一个qemurun.bat文件,将以下内容保存在其中:

@echo off
chcp 65001
"E:\Program Files\qemu\qemu-system-x86_64.exe" -bios "E:\UEFIWorkSpace\Build\OvmfX64\DEBUG_VS2017\FV\OVMF.fd" -M pc -m 256 -cpu "qemu64" -boot order=dc -serial stdio -net none -usbdevice disk:HDD_BOOT.img

(注:根据自己的qemu的安装位置和fd文件的位置进行更改) 

然后运行命令:

qemurun.bat | findstr MyHelloWorldPPI

 其运行结果如下

E:\UEFIWorkSpace\edk2>qemurun.bat | findstr MyHelloWorldPPI
WARNING: Image format was not specified for 'HDD_BOOT.img' and probing guessed raw.
         Automatically detecting the format is dangerous for raw images, write operations on block 0 will be restricted.
         Specify the 'raw' format explicitly to remove the restrictions.
[MyHelloWorldPPI] MyHelloWorldInstallPPIEntry Start..
[MyHelloWorldPPI] MyHelloWorldInstallPPIEntry End..
[MyHelloWorldPPI] MyHelloWorldLocatePPIEntry Locate PPI Start..
[MyHelloWorldPPI] PrintHelloMsg : 2024 CSDN Locate PPI Hello World ...
[MyHelloWorldPPI] MyHelloWorldLocatePPIEntry Locate PPI End..

 结果总结:

MyHelloWorldInstallPPI模块通过PeiServicesInstallPpi提供了mPpiListCodePpi服务(通过 EFI_PEI_PPI_DESCRIPTOR来指定具体的实例),而MyHelloWorldLocatePPI模块通过PeiServicesLocatePpi获取了mPpiListCodePpi对象,通过访问对象的成员函数即可完成对PEIM驱动MyHelloWorldInstallPPI资源的访问。

(注意,要想在运行的时候输出以上DEBUG信息,需要在编译包的时候加上DEBUG_ON_SERIAL_PORT) 

参考博客:https://blog.csdn.net/xiaopangzi313/article/details/89602882


http://www.kler.cn/a/290304.html

相关文章:

  • 【Python】爬虫通过验证码
  • Java-Redisson分布式锁+自定义注解+AOP的方式来实现后台防止重复请求扩展
  • WPF 应用程序中使用 Prism 框架时,有多种方式可以注册服务和依赖项
  • 行业类别-智能制造-子类别工业4.0-细分类别物联网应用-应用场景智能工厂建设
  • 工业通信协议对比:OPC-UA、Modbus、MQTT、HTTP
  • 数据库管理-第260期 业务向前,数据库向后(20241111)
  • FFmpeg源码:avcodec_descriptor_get函数分析
  • Flutter 仿iOS桌面悬浮球效果
  • 【数学建模备赛】Ep07:灰色预测模型
  • 随手笔记【五】
  • 【扇贝编程】使用Selenium模拟浏览器获取动态内容笔记
  • AI证件照生成神器颠覆传统,轻松驾驭考研、考公与签证申请
  • PHP + Redis 实现抽奖算法(ThinkPHP5)
  • Spring6梳理6——依赖注入之Setter注入
  • 【drools】Rulesengine构建及intelj配置
  • 通过组合Self-XSS + CSRF得到存储型XSS
  • 跨境电商代购系统中前台基本功能介绍:帮助更快的了解跨境代购业务
  • 注册登陆(最新版)
  • IOS 18 发现界面(UITableView)Banner轮播图实现
  • 【话题】提升开发效率的秘密武器:探索高效编程工具
  • SpinalHDL之BlackBox(下篇)
  • C#如何使用外部别名Extern alias
  • 单向链表与双向链表
  • 8逻辑回归的代价函数
  • HTTP与TCP的关系是什么?HTTP 的端口有什么意义?
  • ComfyUI SDXL Prompt Styler 简介