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_CALLBACKEFI_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/
来源:爱影博客
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
< <上一篇
下一篇>>
文章目录
关闭
目 录