UEFI开发学习18 – PEIM
概述
PEIM(Pre-EFI Initialization Module)是UEFI在PEI阶段初始化时所用到的一种重要机制,如其名,它把初始化的任务按功能做了模块化,可分为以下几种:
• 特定于平台的 PEIM
• 特定于处理器的 PEIM
• 特定于芯片组的 PEIM
• PEI CIS–prescribed architectural PEIM
• 其他 PEIM
每个PEIM都是一个独立的功能模块,但模块与模块之间又可以互相调用,此称为PEIM-to-PEIM Communication,实现这种可以互相调用的机制称为PPI。PPI的使用方式跟Protocol非常相似,使用之前需要用 InstallPpi() 函数安装在PPI数据库中,然后使用 LocatePpi() 得到该PPI实例(Instance),再调用即可。
Descriptor
PEIM Descriptors 是一个结构体,包含了 标志位(Flags),指向GUID的指针 和 指向数据的指针,若一个PEIM被设计成可被发现的,那它就必须要用 Descriptor 去定义它的接口及其类型;若一个PEIM的代码需要在某个阶段或某个PPI被安装的时候执行,那同样也需要用 Descriptors 去定义。在 PI 手册中,定义了三种Descriptor,分别是 EFI_PEI_DESCRIPTOR 、EFI_PEI_NOTIFY_DESCRIPTOR 和 EFI_PEI_PPI_DESCRIPTOR 。
这里顺便提一下 EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK 跟 EFI_PEI_PPI_DESCRIPTOR_NOTIFY_DISPATCH 的区别,使用前者的Notify函数被触发时将会立即执行,类似于中断,而使用后者的Notify则不会立即执行,它是在调用下一个PEIM之前执行的,假如没有下一个PEIM,那它将不会执行。
PEIM实例
PEIM 写起来跟普通的EFI模块都是差不多的,有几点需要注意:
1. 在INF文件中 MODULE_TYPE 设置为 PEIM
2. 如果有调用到某个PPI,需要将其对应的GUID填在 [Ppis] 这个标签中,跟Protocol类似
3. 一定要加上 [Depex] 标签,值可默认为TRUE,此标签是用于决定PEIM被调用的顺序,比如该模块需要调用到abc这个PPI,那该模块执行的前提就是abc这个PPI已经被安装了,所以需要在此处填上被调用的PPI,如果没有,则可填为TRUE,表示永远可被执行,无依赖关系。
实例1、一个简单的PEIM
下面是一个最简单的PEIM,作用是在PEI阶段向CMOS的0xF0处写一个0x55的值。
// CmosManagerPei.c #include <Base.h> #include <Library/IoLib.h> #include <Library/BaseLib.h> EFI_STATUS EFIAPI CmosManagerPeiEntryPoint ( IN EFI_PEI_FILE_HANDLE FileHandle, IN CONST EFI_PEI_SERVICES ** PeiServices ) { IoWrite8(0x70, 0xF0); IoWrite8(0x71, 0x55); return EFI_SUCCESS; }
# CmosManagerPei.inf [Defines] INF_VERSION = 0x00010005 VERSION_STRING = 1.0 BASE_NAME = CmosManagerPei MODULE_TYPE = PEIM FILE_GUID = 6577023b-69f5-42ca-ac77-3ce69b6d8f33 ENTRY_POINT = CmosManagerPeiEntryPoint [Sources] CmosManagerPei.c [LibraryClasses] IoLib BaseLib PeimEntryPoint [Packages] MdePkg\MdePkg.dec [Pcd] [Ppis] [Depex] TRUE
需要将此模块加入到OvmfPkg中编译,可放到如下目录:
OvmfPkgX64.dsc 跟 OvmfPkgX64.fdf 文件要加入该inf文件编译到固件中。
编译后启动qemu进入shell,通过输入以下命令可检查寄存器的值:
实例2、带有Notify的PEIM
在PEIM中,基本都是有使用Notify的,像实例1中的比较少。为什么要使用Notify呢?因为它可以在特定的条件下触发,比如我要在PEI最后的阶段去执行代码,Notify就派上用场了。下面是相关的实例代码。
// CmosManagerPei.c #include <Base.h> #include <Library/IoLib.h> #include <Library/BaseLib.h> #include <Library/PeiServicesLib.h> EFI_STATUS SetCmosOnEndofPei( IN EFI_PEI_SERVICES **PeiServices, IN EFI_PEI_NOTIFY_DESCRIPTOR *NotifyDescriptor, IN VOID *InvokePpi ); static EFI_PEI_NOTIFY_DESCRIPTOR mNotifyList[] = { { EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST, &gEfiEndOfPeiSignalPpiGuid, SetCmosOnEndofPei } }; EFI_STATUS SetCmosOnEndofPei ( IN EFI_PEI_SERVICES **PeiServices, IN EFI_PEI_NOTIFY_DESCRIPTOR *NotifyDescriptor, IN VOID *InvokePpi ) { IoWrite8(0x70, 0xF1); IoWrite8(0x71, 0x56); return EFI_SUCCESS; } EFI_STATUS CmosManagerPeiEntryPoint ( IN EFI_PEI_FILE_HANDLE FileHandle, IN CONST EFI_PEI_SERVICES ** PeiServices ) { EFI_STATUS Status; Status = (*PeiServices)->NotifyPpi (PeiServices, &mNotifyList[0]); if (EFI_ERROR (Status)) { return Status; } return EFI_SUCCESS; }
# CmosManagerPei.inf [Defines] INF_VERSION = 0x00010005 VERSION_STRING = 1.0 BASE_NAME = CmosManagerPei MODULE_TYPE = PEIM FILE_GUID = 6577023b-69f5-42ca-ac77-3ce69b6d8f33 ENTRY_POINT = CmosManagerPeiEntryPoint [Sources] CmosManagerPei.c [LibraryClasses] IoLib BaseLib PeimEntryPoint [Packages] MdePkg\MdePkg.dec [Pcd] [Ppis] gEfiEndOfPeiSignalPpiGuid [Depex] TRUE
执行结果:
程序是怎么执行的呢?
1.在PEI阶段Dispatch到该PEIM的时候,从入口函数 CmosManagerPeiEntryPoint 开始执行,注册一个Notify函数 SetCmosOnEndofPei 。
2.当Dispatch到安装 gEfiEndOfPeiSignalPpiGuid 这个PPI的时候,立即跳转执行 SetCmosOnEndofPei 函数。
实例3、安装一个PPI
当需要将一个PEIM的代码共享给其它PEIM调用的时候,就可以把它安装在PPI的数据库中。如一个常用的PPI-> gEfiPeiReadOnlyVariable2PpiGuid ,它安装在PPI数据库中,可以被其它PEIM用于获取系统中的Variable,示例代码如下:
// CmosManagerPei.c #include <Base.h> #include <Uefi.h> #include <Library/UefiLib.h> #include <Library/IoLib.h> #include <Library/BaseLib.h> #include <Library/PeiServicesLib.h> EFI_GUID gEfiCmosManagerPpiGuid = {0xbec47213, 0x39da, 0x4352, {0x9f, 0x12, 0xa1, 0x37, 0x8f, 0xb3, 0xfb, 0x44}}; EFI_STATUS SetCmosOnEndofPei( IN EFI_PEI_SERVICES **PeiServices, IN EFI_PEI_NOTIFY_DESCRIPTOR *NotifyDescriptor, IN VOID *InvokePpi ); EFI_STATUS CmosManagerInstallNotify( IN EFI_PEI_SERVICES **PeiServices, IN EFI_PEI_NOTIFY_DESCRIPTOR *NotifyDescriptor, IN VOID *InvokePpi ); static EFI_PEI_NOTIFY_DESCRIPTOR mNotifyList[] = { { EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK, &gEfiEndOfPeiSignalPpiGuid, SetCmosOnEndofPei }, { EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST, &gEfiCmosManagerPpiGuid, CmosManagerInstallNotify } }; EFI_STATUS SetCmosOnEndofPei ( IN EFI_PEI_SERVICES **PeiServices, IN EFI_PEI_NOTIFY_DESCRIPTOR *NotifyDescriptor, IN VOID *InvokePpi ) { IoWrite8(0x70, 0xF1); IoWrite8(0x71, 0xAA); return EFI_SUCCESS; } /*************************************************************************** * Example 3, Install a PEIM * ****************************************************************************/ typedef struct _EFI_PEI_CMOS_MANAGER_PPI EFI_PEI_CMOS_MANAGER_PPI; typedef EFI_STATUS (EFIAPI *EFI_PEI_CMOS_READ)( IN UINTN Index, OUT UINTN *Value ); typedef EFI_STATUS (EFIAPI *EFI_PEI_CMOS_WRITE)( IN UINTN Index, IN UINTN Value ); struct _EFI_PEI_CMOS_MANAGER_PPI { EFI_PEI_CMOS_READ PeiCmosRead; EFI_PEI_CMOS_WRITE PeiCmosWrite; }; EFI_STATUS EFIAPI CmosManagerRead( IN UINTN Index, OUT UINTN *Value ); EFI_STATUS EFIAPI CmosManagerWrite( IN UINTN Index, IN UINTN Value ); EFI_PEI_CMOS_MANAGER_PPI mCmosManagerPpi = { CmosManagerRead, CmosManagerWrite }; EFI_PEI_PPI_DESCRIPTOR mCmosManagerPpiList = { (EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST), &gEfiCmosManagerPpiGuid, &mCmosManagerPpi }; EFI_STATUS EFIAPI CmosManagerRead( IN UINTN Index, OUT UINTN *Value ){ IoWrite8(0x70, (UINT8)Index); *Value = IoRead8(0x71); return EFI_SUCCESS; } EFI_STATUS EFIAPI CmosManagerWrite( IN UINTN Index, IN UINTN Value ){ IoWrite8(0x70, (UINT8)Index); IoWrite8(0x71, (UINT8)Value); return EFI_SUCCESS; } EFI_STATUS CmosManagerInstallNotify( IN EFI_PEI_SERVICES **PeiServices, IN EFI_PEI_NOTIFY_DESCRIPTOR *NotifyDescriptor, IN VOID *InvokePpi ) { EFI_STATUS Status; EFI_PEI_CMOS_MANAGER_PPI *CmosManagerPpi; Status = PeiServicesLocatePpi (&gEfiCmosManagerPpiGuid, 0, NULL, (VOID **) &CmosManagerPpi); if (!EFI_ERROR (Status)) { CmosManagerPpi->PeiCmosWrite(0xFD, 0xAA); } return EFI_SUCCESS; } // Example 3 end EFI_STATUS CmosManagerPeiEntryPoint ( IN EFI_PEI_FILE_HANDLE FileHandle, IN CONST EFI_PEI_SERVICES ** PeiServices ) { EFI_STATUS Status; Status = (*PeiServices)->NotifyPpi (PeiServices, mNotifyList); if (EFI_ERROR (Status)) { return Status; } Status = (*PeiServices)->InstallPpi (PeiServices, &mCmosManagerPpiList); if (EFI_ERROR (Status)) { return Status; } return EFI_SUCCESS; }
以上代码探究了一个PPI的安装与调用的过程。代码先是在入口函数注册了两个Notify函数,分别是实例2中的 SetCmosOnEndofPei 和本例添加的 CmosManagerInstallNotify,然后再安装了一个名为 EFI_PEI_CMOS_MANAGER_PPI 的PPI,当该PPI被安装时,与其对应的Notify函数将被调用。为了简化演示的过程,我将调用该PPI的代码写到了Notify函数中,调用成功向CMOS 0xFD处写入0xAA。自己实践的时候可以多写一个PEIM,然后再去调用也可。
附件
https://gitee.com/ay123net/uefistudy
版权声明:
作者:bin
链接:https://ay123.net/mystudy/uefi/1233/
来源:爱影博客
文章版权归作者所有,未经允许请勿转载。
共有 0 条评论