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文件编译到固件中。

实例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 条评论